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:
Simon Detheridge 2019-02-25 10:24:10 +00:00 committed by James Allen
parent c6b8ab3245
commit f84e94dc88

View file

@ -1,22 +1,4 @@
/* eslint-disable
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
*/
/* global _ */
define([
'ide/editor/directives/aceEditor/auto-complete/CommandManager',
'ide/editor/directives/aceEditor/auto-complete/EnvironmentManager',
@ -25,11 +7,10 @@ define([
'ace/ace',
'ace/ext-language_tools'
], function(CommandManager, EnvironmentManager, PackageManager, Helpers) {
let AutoCompleteManager
const { Range } = ace.require('ace/range')
const aceSnippetManager = ace.require('ace/snippets').snippetManager
return (AutoCompleteManager = class AutoCompleteManager {
class AutoCompleteManager {
constructor(
$scope,
editor,
@ -50,19 +31,19 @@ define([
this.$scope.$watch('autoComplete', autocomplete => {
if (autocomplete) {
return this.enable()
this.enable()
} else {
return this.disable()
this.disable()
}
})
const onChange = change => {
return this.onChange(change)
this.onChange(change)
}
this.editor.on('changeSession', e => {
e.oldSession.off('change', onChange)
return e.session.on('change', onChange)
e.session.on('change', onChange)
})
}
@ -89,14 +70,13 @@ define([
/^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/
)
if (match) {
let [_ignore1, commandName, _ignore2, currentArg] = Array.from(
match
)
// eslint-disable-next-line no-unused-vars
let commandName = match[1]
const graphicsPaths = Preamble.getGraphicsPaths()
const result = []
for (let graphic of Array.from(Graphics.getGraphicsFiles())) {
for (let graphic of Graphics.getGraphicsFiles()) {
let { path } = graphic
for (let graphicsPath of Array.from(graphicsPaths)) {
for (let graphicsPath of graphicsPaths) {
if (path.indexOf(graphicsPath) === 0) {
path = path.slice(graphicsPath.length)
break
@ -109,7 +89,7 @@ define([
score: 50
})
}
return callback(null, result)
callback(null, result)
}
}
}
@ -122,9 +102,10 @@ define([
if (commandFragment) {
const match = commandFragment.match(/^\\(input|include){(\w*)/)
if (match) {
const [_, commandName, currentArg] = Array.from(match)
// eslint-disable-next-line no-unused-vars
const commandName = match[1]
const result = []
for (let file of Array.from(Files.getTeXFiles())) {
for (let file of Files.getTeXFiles()) {
if (file.id !== this.$scope.docId) {
const { path } = file
result.push({
@ -135,7 +116,7 @@ define([
})
}
}
return callback(null, result)
callback(null, result)
}
}
}
@ -149,7 +130,8 @@ define([
/^~?\\([a-zA-Z]*ref){([^}]*, *)?(\w*)/
)
if (refMatch) {
const [_, commandName, currentArg] = Array.from(refMatch)
// eslint-disable-next-line no-unused-vars
const commandName = refMatch[1]
const result = []
if (commandName !== 'ref') {
// ref is in top 100 commands
@ -160,7 +142,7 @@ define([
score: 60
})
}
for (let label of Array.from(metadataManager.getAllLabels())) {
for (let label of metadataManager.getAllLabels()) {
result.push({
caption: `\\${commandName}{${label}}`,
value: `\\${commandName}{${label}}`,
@ -168,7 +150,7 @@ define([
score: 50
})
}
return callback(null, result)
callback(null, result)
}
}
}
@ -183,9 +165,8 @@ define([
/^~?\\([a-z]*cite[a-z]*(?:\[.*])?){([^}]*, *)?(\w*)/
)
if (citeMatch) {
let [_, commandName, previousArgs, currentArg] = Array.from(
citeMatch
)
// eslint-disable-next-line no-unused-vars
let [_ignore, commandName, previousArgs] = citeMatch
if (previousArgs == null) {
previousArgs = ''
}
@ -209,16 +190,16 @@ define([
})
}
})
return callback(null, result)
callback(null, result)
} else {
return callback(null, result)
callback(null, result)
}
}
}
}
}
return (this.editor.completers = [
this.editor.completers = [
CommandCompleter,
SnippetCompleter,
PackageCompleter,
@ -226,7 +207,7 @@ define([
LabelsCompleter,
GraphicsCompleter,
FilesCompleter
])
]
}
disable() {
@ -254,16 +235,15 @@ define([
const lastTwoChars = lineUpToCursor.slice(-2)
// Don't offer autocomplete on double-backslash, backslash-colon, etc
if (/^\\[^a-zA-Z]$/.test(lastTwoChars)) {
__guardMethod__(
this.editor != null ? this.editor.completer : undefined,
'detach',
o => o.detach()
)
if (this.editor.completer) {
this.editor.completer.detach()
}
return
}
// Check that this change was made by us, not a collaborator
// (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 (
change.action === 'insert' &&
end.row === cursorPosition.row &&
@ -274,18 +254,21 @@ define([
lastCharIsBackslash
) {
setTimeout(() => {
return this.editor.execCommand('startAutocomplete')
this.editor.execCommand('startAutocomplete')
}, 0)
}
}
const match = change.lines[0].match(/\\(\w+){}/)
if (
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(
__guard__(change.lines[0].match(/\\(\w+){}/), x => x[1])
match[1]
)
) {
return setTimeout(() => {
return this.editor.execCommand('startAutocomplete')
this.editor.execCommand('startAutocomplete')
}, 0)
}
}
@ -306,16 +289,21 @@ define([
// 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}...
if (/^\\\w+{/.test(this.completions.filterText) && nextChar === '}') {
if (
/^\\\w+(\[[\w\\,= ]*\])?{/.test(this.completions.filterText) &&
nextChar === '}'
) {
editor.session.remove(range)
}
// Provide our own `insertMatch` implementation.
// See the `insertMatch` method of Autocomplete in `ext-language_tools.js`.
// We need this to account for editing existing commands, particularly when
// adding a prefix.
// We fix this by detecting when the cursor is in the middle of an existing
// command, and adjusting the insertions/deletions accordingly.
// See the `insertMatch` method of Autocomplete in
// `ext-language_tools.js`.
// We need this to account for editing existing commands, particularly
// when adding a prefix.
// We fix this by detecting when the cursor is in the middle of an
// existing command, and adjusting the insertions/deletions
// accordingly.
// Example:
// when changing `\ref{}` to `\href{}`, ace default behaviour
// is likely to end up with `\href{}ref{}`
@ -325,7 +313,7 @@ define([
data = popup.getData(popup.getRow())
data.completer = {
insertMatch(editor, matchData) {
for (range of Array.from(editor.selection.getAllRanges())) {
for (range of editor.selection.getAllRanges()) {
const leftRange = _.clone(range)
const rightRange = _.clone(range)
// trim to left of cursor
@ -362,35 +350,34 @@ define([
)
if (lineBeyondCursor) {
var partialCommandMatch
if (
(partialCommandMatch = lineBeyondCursor.match(
const partialCommandMatch = lineBeyondCursor.match(
/^([a-zA-Z0-9]+)\{/
))
) {
)
if (partialCommandMatch) {
// We've got a partial command after the cursor
const commandTail = partialCommandMatch[1]
// remove rest of the partial command, right of cursor
rightRange.end.column +=
commandTail.length - completions.filterText.length
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'
if (matchData.snippet != null) {
matchData.snippet = matchData.snippet.replace(
/[{\[].*[}\]]/,
/[{[].*[}\]]/,
''
)
}
if (matchData.caption != null) {
matchData.caption = matchData.caption.replace(
/[{\[].*[}\]]/,
/[{[].*[}\]]/,
''
)
}
if (matchData.value != null) {
matchData.value = matchData.value.replace(
/[{\[].*[}\]]/,
/[{[].*[}\]]/,
''
)
}
@ -399,12 +386,9 @@ define([
}
// finally, insert the match
if (matchData.snippet) {
return aceSnippetManager.insertSnippet(
editor,
matchData.snippet
)
aceSnippetManager.insertSnippet(editor, matchData.snippet)
} else {
return editor.execCommand(
editor.execCommand(
'insertstring',
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
@ -435,15 +419,10 @@ define([
)
container.css({ 'font-size': this.$scope.fontSize + 'px' })
// Dynamically set width of autocomplete popup
if (
(filtered = __guard__(
__guard__(
editor != null ? editor.completer : undefined,
x1 => x1.completions
),
x => x.filtered
))
) {
filtered =
editor.completer.completions &&
editor.completer.completions.filtered
if (filtered) {
const longestCaption = _.max(filtered.map(c => c.caption.length))
const longestMeta = _.max(filtered.map(c => c.meta.length))
const charWidth = editor.renderer.characterWidth
@ -461,31 +440,17 @@ define([
)
container.css({ width: `${width}px` })
}
if (
__guard__(
__guard__(
editor.completer != null
? editor.completer.completions
: undefined,
x3 => x3.filtered
),
x2 => x2.length
) === 0
) {
return editor.completer.detach()
if (filtered.length === 0) {
editor.completer.detach()
}
},
bindKey: 'Ctrl-Space|Ctrl-Shift-Space|Alt-Space'
}
}
return (Util.retrievePrecedingIdentifier = function(text, pos, regex) {
Util.retrievePrecedingIdentifier = function(text, pos, regex) {
let currentLineOffset = 0
for (
let start = pos - 1, i = start, asc = start <= 0;
asc ? i <= 0 : i >= 0;
asc ? i++ : i--
) {
for (let i = pos - 1; i <= 0; i++) {
if (text[i] === '\n') {
currentLineOffset = i + 1
break
@ -494,24 +459,9 @@ define([
const currentLine = text.slice(currentLineOffset, pos)
const fragment = Helpers.getLastCommandFragment(currentLine) || ''
return fragment
})
}
})
})
}
}
function __guardMethod__(obj, methodName, transform) {
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
}
return AutoCompleteManager
})