mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #1533 from sharelatex/as-fix-extra-bracket
Fix autocomplete inserting extra brace when completing command with optional argument GitOrigin-RevId: cb01eb0b7df400997e5f7dc25a92ef7660689709
This commit is contained in:
parent
c6b8ab3245
commit
f84e94dc88
1 changed files with 72 additions and 122 deletions
|
@ -1,22 +1,4 @@
|
||||||
/* eslint-disable
|
/* global _ */
|
||||||
max-len,
|
|
||||||
no-cond-assign,
|
|
||||||
no-return-assign,
|
|
||||||
no-undef,
|
|
||||||
no-unused-vars,
|
|
||||||
no-useless-escape,
|
|
||||||
*/
|
|
||||||
// TODO: This file was created by bulk-decaffeinate.
|
|
||||||
// Fix any style issues and re-enable lint.
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS101: Remove unnecessary use of Array.from
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS103: Rewrite code to no longer use __guard__
|
|
||||||
* DS202: Simplify dynamic range loops
|
|
||||||
* DS207: Consider shorter variations of null checks
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
define([
|
define([
|
||||||
'ide/editor/directives/aceEditor/auto-complete/CommandManager',
|
'ide/editor/directives/aceEditor/auto-complete/CommandManager',
|
||||||
'ide/editor/directives/aceEditor/auto-complete/EnvironmentManager',
|
'ide/editor/directives/aceEditor/auto-complete/EnvironmentManager',
|
||||||
|
@ -25,11 +7,10 @@ define([
|
||||||
'ace/ace',
|
'ace/ace',
|
||||||
'ace/ext-language_tools'
|
'ace/ext-language_tools'
|
||||||
], function(CommandManager, EnvironmentManager, PackageManager, Helpers) {
|
], function(CommandManager, EnvironmentManager, PackageManager, Helpers) {
|
||||||
let AutoCompleteManager
|
|
||||||
const { Range } = ace.require('ace/range')
|
const { Range } = ace.require('ace/range')
|
||||||
const aceSnippetManager = ace.require('ace/snippets').snippetManager
|
const aceSnippetManager = ace.require('ace/snippets').snippetManager
|
||||||
|
|
||||||
return (AutoCompleteManager = class AutoCompleteManager {
|
class AutoCompleteManager {
|
||||||
constructor(
|
constructor(
|
||||||
$scope,
|
$scope,
|
||||||
editor,
|
editor,
|
||||||
|
@ -50,19 +31,19 @@ define([
|
||||||
|
|
||||||
this.$scope.$watch('autoComplete', autocomplete => {
|
this.$scope.$watch('autoComplete', autocomplete => {
|
||||||
if (autocomplete) {
|
if (autocomplete) {
|
||||||
return this.enable()
|
this.enable()
|
||||||
} else {
|
} else {
|
||||||
return this.disable()
|
this.disable()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onChange = change => {
|
const onChange = change => {
|
||||||
return this.onChange(change)
|
this.onChange(change)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.on('changeSession', e => {
|
this.editor.on('changeSession', e => {
|
||||||
e.oldSession.off('change', onChange)
|
e.oldSession.off('change', onChange)
|
||||||
return e.session.on('change', onChange)
|
e.session.on('change', onChange)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,14 +70,13 @@ define([
|
||||||
/^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/
|
/^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/
|
||||||
)
|
)
|
||||||
if (match) {
|
if (match) {
|
||||||
let [_ignore1, commandName, _ignore2, currentArg] = Array.from(
|
// eslint-disable-next-line no-unused-vars
|
||||||
match
|
let commandName = match[1]
|
||||||
)
|
|
||||||
const graphicsPaths = Preamble.getGraphicsPaths()
|
const graphicsPaths = Preamble.getGraphicsPaths()
|
||||||
const result = []
|
const result = []
|
||||||
for (let graphic of Array.from(Graphics.getGraphicsFiles())) {
|
for (let graphic of Graphics.getGraphicsFiles()) {
|
||||||
let { path } = graphic
|
let { path } = graphic
|
||||||
for (let graphicsPath of Array.from(graphicsPaths)) {
|
for (let graphicsPath of graphicsPaths) {
|
||||||
if (path.indexOf(graphicsPath) === 0) {
|
if (path.indexOf(graphicsPath) === 0) {
|
||||||
path = path.slice(graphicsPath.length)
|
path = path.slice(graphicsPath.length)
|
||||||
break
|
break
|
||||||
|
@ -109,7 +89,7 @@ define([
|
||||||
score: 50
|
score: 50
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return callback(null, result)
|
callback(null, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,9 +102,10 @@ define([
|
||||||
if (commandFragment) {
|
if (commandFragment) {
|
||||||
const match = commandFragment.match(/^\\(input|include){(\w*)/)
|
const match = commandFragment.match(/^\\(input|include){(\w*)/)
|
||||||
if (match) {
|
if (match) {
|
||||||
const [_, commandName, currentArg] = Array.from(match)
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const commandName = match[1]
|
||||||
const result = []
|
const result = []
|
||||||
for (let file of Array.from(Files.getTeXFiles())) {
|
for (let file of Files.getTeXFiles()) {
|
||||||
if (file.id !== this.$scope.docId) {
|
if (file.id !== this.$scope.docId) {
|
||||||
const { path } = file
|
const { path } = file
|
||||||
result.push({
|
result.push({
|
||||||
|
@ -135,7 +116,7 @@ define([
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callback(null, result)
|
callback(null, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +130,8 @@ define([
|
||||||
/^~?\\([a-zA-Z]*ref){([^}]*, *)?(\w*)/
|
/^~?\\([a-zA-Z]*ref){([^}]*, *)?(\w*)/
|
||||||
)
|
)
|
||||||
if (refMatch) {
|
if (refMatch) {
|
||||||
const [_, commandName, currentArg] = Array.from(refMatch)
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const commandName = refMatch[1]
|
||||||
const result = []
|
const result = []
|
||||||
if (commandName !== 'ref') {
|
if (commandName !== 'ref') {
|
||||||
// ref is in top 100 commands
|
// ref is in top 100 commands
|
||||||
|
@ -160,7 +142,7 @@ define([
|
||||||
score: 60
|
score: 60
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for (let label of Array.from(metadataManager.getAllLabels())) {
|
for (let label of metadataManager.getAllLabels()) {
|
||||||
result.push({
|
result.push({
|
||||||
caption: `\\${commandName}{${label}}`,
|
caption: `\\${commandName}{${label}}`,
|
||||||
value: `\\${commandName}{${label}}`,
|
value: `\\${commandName}{${label}}`,
|
||||||
|
@ -168,7 +150,7 @@ define([
|
||||||
score: 50
|
score: 50
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return callback(null, result)
|
callback(null, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,9 +165,8 @@ define([
|
||||||
/^~?\\([a-z]*cite[a-z]*(?:\[.*])?){([^}]*, *)?(\w*)/
|
/^~?\\([a-z]*cite[a-z]*(?:\[.*])?){([^}]*, *)?(\w*)/
|
||||||
)
|
)
|
||||||
if (citeMatch) {
|
if (citeMatch) {
|
||||||
let [_, commandName, previousArgs, currentArg] = Array.from(
|
// eslint-disable-next-line no-unused-vars
|
||||||
citeMatch
|
let [_ignore, commandName, previousArgs] = citeMatch
|
||||||
)
|
|
||||||
if (previousArgs == null) {
|
if (previousArgs == null) {
|
||||||
previousArgs = ''
|
previousArgs = ''
|
||||||
}
|
}
|
||||||
|
@ -209,16 +190,16 @@ define([
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return callback(null, result)
|
callback(null, result)
|
||||||
} else {
|
} else {
|
||||||
return callback(null, result)
|
callback(null, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (this.editor.completers = [
|
this.editor.completers = [
|
||||||
CommandCompleter,
|
CommandCompleter,
|
||||||
SnippetCompleter,
|
SnippetCompleter,
|
||||||
PackageCompleter,
|
PackageCompleter,
|
||||||
|
@ -226,7 +207,7 @@ define([
|
||||||
LabelsCompleter,
|
LabelsCompleter,
|
||||||
GraphicsCompleter,
|
GraphicsCompleter,
|
||||||
FilesCompleter
|
FilesCompleter
|
||||||
])
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
|
@ -254,16 +235,15 @@ define([
|
||||||
const lastTwoChars = lineUpToCursor.slice(-2)
|
const lastTwoChars = lineUpToCursor.slice(-2)
|
||||||
// Don't offer autocomplete on double-backslash, backslash-colon, etc
|
// Don't offer autocomplete on double-backslash, backslash-colon, etc
|
||||||
if (/^\\[^a-zA-Z]$/.test(lastTwoChars)) {
|
if (/^\\[^a-zA-Z]$/.test(lastTwoChars)) {
|
||||||
__guardMethod__(
|
if (this.editor.completer) {
|
||||||
this.editor != null ? this.editor.completer : undefined,
|
this.editor.completer.detach()
|
||||||
'detach',
|
}
|
||||||
o => o.detach()
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Check that this change was made by us, not a collaborator
|
// Check that this change was made by us, not a collaborator
|
||||||
// (Cursor is still one place behind)
|
// (Cursor is still one place behind)
|
||||||
// NOTE: this is also the case when a user backspaces over a highlighted region
|
// NOTE: this is also the case when a user backspaces over a highlighted
|
||||||
|
// region
|
||||||
if (
|
if (
|
||||||
change.action === 'insert' &&
|
change.action === 'insert' &&
|
||||||
end.row === cursorPosition.row &&
|
end.row === cursorPosition.row &&
|
||||||
|
@ -274,18 +254,21 @@ define([
|
||||||
lastCharIsBackslash
|
lastCharIsBackslash
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
return this.editor.execCommand('startAutocomplete')
|
this.editor.execCommand('startAutocomplete')
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const match = change.lines[0].match(/\\(\w+){}/)
|
||||||
if (
|
if (
|
||||||
change.action === 'insert' &&
|
change.action === 'insert' &&
|
||||||
|
(match && match[1]) &&
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
/(begin|end|[a-zA-Z]*ref|usepackage|[a-z]*cite[a-z]*|input|include)/.test(
|
/(begin|end|[a-zA-Z]*ref|usepackage|[a-z]*cite[a-z]*|input|include)/.test(
|
||||||
__guard__(change.lines[0].match(/\\(\w+){}/), x => x[1])
|
match[1]
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return setTimeout(() => {
|
return setTimeout(() => {
|
||||||
return this.editor.execCommand('startAutocomplete')
|
this.editor.execCommand('startAutocomplete')
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,16 +289,21 @@ define([
|
||||||
|
|
||||||
// If we are in \begin{it|}, then we need to remove the trailing }
|
// If we are in \begin{it|}, then we need to remove the trailing }
|
||||||
// since it will be adding in with the autocomplete of \begin{item}...
|
// since it will be adding in with the autocomplete of \begin{item}...
|
||||||
if (/^\\\w+{/.test(this.completions.filterText) && nextChar === '}') {
|
if (
|
||||||
|
/^\\\w+(\[[\w\\,= ]*\])?{/.test(this.completions.filterText) &&
|
||||||
|
nextChar === '}'
|
||||||
|
) {
|
||||||
editor.session.remove(range)
|
editor.session.remove(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide our own `insertMatch` implementation.
|
// Provide our own `insertMatch` implementation.
|
||||||
// See the `insertMatch` method of Autocomplete in `ext-language_tools.js`.
|
// See the `insertMatch` method of Autocomplete in
|
||||||
// We need this to account for editing existing commands, particularly when
|
// `ext-language_tools.js`.
|
||||||
// adding a prefix.
|
// We need this to account for editing existing commands, particularly
|
||||||
// We fix this by detecting when the cursor is in the middle of an existing
|
// when adding a prefix.
|
||||||
// command, and adjusting the insertions/deletions accordingly.
|
// We fix this by detecting when the cursor is in the middle of an
|
||||||
|
// existing command, and adjusting the insertions/deletions
|
||||||
|
// accordingly.
|
||||||
// Example:
|
// Example:
|
||||||
// when changing `\ref{}` to `\href{}`, ace default behaviour
|
// when changing `\ref{}` to `\href{}`, ace default behaviour
|
||||||
// is likely to end up with `\href{}ref{}`
|
// is likely to end up with `\href{}ref{}`
|
||||||
|
@ -325,7 +313,7 @@ define([
|
||||||
data = popup.getData(popup.getRow())
|
data = popup.getData(popup.getRow())
|
||||||
data.completer = {
|
data.completer = {
|
||||||
insertMatch(editor, matchData) {
|
insertMatch(editor, matchData) {
|
||||||
for (range of Array.from(editor.selection.getAllRanges())) {
|
for (range of editor.selection.getAllRanges()) {
|
||||||
const leftRange = _.clone(range)
|
const leftRange = _.clone(range)
|
||||||
const rightRange = _.clone(range)
|
const rightRange = _.clone(range)
|
||||||
// trim to left of cursor
|
// trim to left of cursor
|
||||||
|
@ -362,35 +350,34 @@ define([
|
||||||
)
|
)
|
||||||
|
|
||||||
if (lineBeyondCursor) {
|
if (lineBeyondCursor) {
|
||||||
var partialCommandMatch
|
const partialCommandMatch = lineBeyondCursor.match(
|
||||||
if (
|
|
||||||
(partialCommandMatch = lineBeyondCursor.match(
|
|
||||||
/^([a-zA-Z0-9]+)\{/
|
/^([a-zA-Z0-9]+)\{/
|
||||||
))
|
)
|
||||||
) {
|
if (partialCommandMatch) {
|
||||||
// We've got a partial command after the cursor
|
// We've got a partial command after the cursor
|
||||||
const commandTail = partialCommandMatch[1]
|
const commandTail = partialCommandMatch[1]
|
||||||
// remove rest of the partial command, right of cursor
|
// remove rest of the partial command, right of cursor
|
||||||
rightRange.end.column +=
|
rightRange.end.column +=
|
||||||
commandTail.length - completions.filterText.length
|
commandTail.length - completions.filterText.length
|
||||||
editor.session.remove(rightRange)
|
editor.session.remove(rightRange)
|
||||||
// trim the completion text to just the command, without braces or brackets
|
// trim the completion text to just the command, without
|
||||||
|
// braces or brackets
|
||||||
// example: '\cite{}' -> '\cite'
|
// example: '\cite{}' -> '\cite'
|
||||||
if (matchData.snippet != null) {
|
if (matchData.snippet != null) {
|
||||||
matchData.snippet = matchData.snippet.replace(
|
matchData.snippet = matchData.snippet.replace(
|
||||||
/[{\[].*[}\]]/,
|
/[{[].*[}\]]/,
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (matchData.caption != null) {
|
if (matchData.caption != null) {
|
||||||
matchData.caption = matchData.caption.replace(
|
matchData.caption = matchData.caption.replace(
|
||||||
/[{\[].*[}\]]/,
|
/[{[].*[}\]]/,
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (matchData.value != null) {
|
if (matchData.value != null) {
|
||||||
matchData.value = matchData.value.replace(
|
matchData.value = matchData.value.replace(
|
||||||
/[{\[].*[}\]]/,
|
/[{[].*[}\]]/,
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -399,12 +386,9 @@ define([
|
||||||
}
|
}
|
||||||
// finally, insert the match
|
// finally, insert the match
|
||||||
if (matchData.snippet) {
|
if (matchData.snippet) {
|
||||||
return aceSnippetManager.insertSnippet(
|
aceSnippetManager.insertSnippet(editor, matchData.snippet)
|
||||||
editor,
|
|
||||||
matchData.snippet
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
return editor.execCommand(
|
editor.execCommand(
|
||||||
'insertstring',
|
'insertstring',
|
||||||
matchData.value || matchData
|
matchData.value || matchData
|
||||||
)
|
)
|
||||||
|
@ -413,7 +397,7 @@ define([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Autocomplete.prototype._insertMatch.call(this, data)
|
Autocomplete.prototype._insertMatch.call(this, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overwrite this to set autoInsert = false and set font size
|
// Overwrite this to set autoInsert = false and set font size
|
||||||
|
@ -435,15 +419,10 @@ define([
|
||||||
)
|
)
|
||||||
container.css({ 'font-size': this.$scope.fontSize + 'px' })
|
container.css({ 'font-size': this.$scope.fontSize + 'px' })
|
||||||
// Dynamically set width of autocomplete popup
|
// Dynamically set width of autocomplete popup
|
||||||
if (
|
filtered =
|
||||||
(filtered = __guard__(
|
editor.completer.completions &&
|
||||||
__guard__(
|
editor.completer.completions.filtered
|
||||||
editor != null ? editor.completer : undefined,
|
if (filtered) {
|
||||||
x1 => x1.completions
|
|
||||||
),
|
|
||||||
x => x.filtered
|
|
||||||
))
|
|
||||||
) {
|
|
||||||
const longestCaption = _.max(filtered.map(c => c.caption.length))
|
const longestCaption = _.max(filtered.map(c => c.caption.length))
|
||||||
const longestMeta = _.max(filtered.map(c => c.meta.length))
|
const longestMeta = _.max(filtered.map(c => c.meta.length))
|
||||||
const charWidth = editor.renderer.characterWidth
|
const charWidth = editor.renderer.characterWidth
|
||||||
|
@ -461,31 +440,17 @@ define([
|
||||||
)
|
)
|
||||||
container.css({ width: `${width}px` })
|
container.css({ width: `${width}px` })
|
||||||
}
|
}
|
||||||
if (
|
if (filtered.length === 0) {
|
||||||
__guard__(
|
editor.completer.detach()
|
||||||
__guard__(
|
|
||||||
editor.completer != null
|
|
||||||
? editor.completer.completions
|
|
||||||
: undefined,
|
|
||||||
x3 => x3.filtered
|
|
||||||
),
|
|
||||||
x2 => x2.length
|
|
||||||
) === 0
|
|
||||||
) {
|
|
||||||
return editor.completer.detach()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bindKey: 'Ctrl-Space|Ctrl-Shift-Space|Alt-Space'
|
bindKey: 'Ctrl-Space|Ctrl-Shift-Space|Alt-Space'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Util.retrievePrecedingIdentifier = function(text, pos, regex) {
|
Util.retrievePrecedingIdentifier = function(text, pos, regex) {
|
||||||
let currentLineOffset = 0
|
let currentLineOffset = 0
|
||||||
for (
|
for (let i = pos - 1; i <= 0; i++) {
|
||||||
let start = pos - 1, i = start, asc = start <= 0;
|
|
||||||
asc ? i <= 0 : i >= 0;
|
|
||||||
asc ? i++ : i--
|
|
||||||
) {
|
|
||||||
if (text[i] === '\n') {
|
if (text[i] === '\n') {
|
||||||
currentLineOffset = i + 1
|
currentLineOffset = i + 1
|
||||||
break
|
break
|
||||||
|
@ -494,24 +459,9 @@ define([
|
||||||
const currentLine = text.slice(currentLineOffset, pos)
|
const currentLine = text.slice(currentLineOffset, pos)
|
||||||
const fragment = Helpers.getLastCommandFragment(currentLine) || ''
|
const fragment = Helpers.getLastCommandFragment(currentLine) || ''
|
||||||
return fragment
|
return fragment
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
function __guardMethod__(obj, methodName, transform) {
|
return AutoCompleteManager
|
||||||
if (
|
})
|
||||||
typeof obj !== 'undefined' &&
|
|
||||||
obj !== null &&
|
|
||||||
typeof obj[methodName] === 'function'
|
|
||||||
) {
|
|
||||||
return transform(obj, methodName)
|
|
||||||
} else {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function __guard__(value, transform) {
|
|
||||||
return typeof value !== 'undefined' && value !== null
|
|
||||||
? transform(value)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue