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 /* 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
}