diff --git a/ace-internal.d.ts b/ace-internal.d.ts index ebb8184c245..2b82b6deb8c 100644 --- a/ace-internal.d.ts +++ b/ace-internal.d.ts @@ -976,7 +976,7 @@ export namespace Ace { interface Completer { /** Regular expressions defining valid identifier characters for completion triggers */ - identifierRegexps?: Array, + identifierRegexps?: Array | ((editor: Editor) => Array), /** Main completion method that provides suggestions for the given context */ getCompletions(editor: Editor, @@ -999,7 +999,7 @@ export namespace Ace { /** Unique identifier for this completer */ id?: string; /** Characters that trigger autocompletion when typed */ - triggerCharacters?: string[]; + triggerCharacters?: string[] | ((editor: Editor) => string[]); /** Whether to hide inline preview text */ hideInlinePreview?: boolean; /** Custom insertion handler for completion items */ diff --git a/ace.d.ts b/ace.d.ts index 41cfd990513..b7f9f2bf23b 100644 --- a/ace.d.ts +++ b/ace.d.ts @@ -783,7 +783,7 @@ declare module "ace-code" { type CompleterCallback = (error: any, completions: Completion[]) => void; interface Completer { /** Regular expressions defining valid identifier characters for completion triggers */ - identifierRegexps?: Array; + identifierRegexps?: Array | ((editor: Editor) => Array); /** Main completion method that provides suggestions for the given context */ getCompletions(editor: Editor, session: EditSession, position: Point, prefix: string, callback: CompleterCallback): void; /** Returns documentation tooltip for a completion item */ @@ -797,7 +797,7 @@ declare module "ace-code" { /** Unique identifier for this completer */ id?: string; /** Characters that trigger autocompletion when typed */ - triggerCharacters?: string[]; + triggerCharacters?: string[] | ((editor: Editor) => string[]); /** Whether to hide inline preview text */ hideInlinePreview?: boolean; /** Custom insertion handler for completion items */ diff --git a/src/autocomplete/util.js b/src/autocomplete/util.js index 514e2da2743..0c9ce1ac420 100644 --- a/src/autocomplete/util.js +++ b/src/autocomplete/util.js @@ -54,10 +54,18 @@ exports.getCompletionPrefix = function (editor) { var prefix; editor.completers.forEach(function(completer) { if (completer.identifierRegexps) { - completer.identifierRegexps.forEach(function(identifierRegex) { - if (!prefix && identifierRegex) - prefix = this.retrievePrecedingIdentifier(line, pos.column, identifierRegex); - }.bind(this)); + var identifierRegexps; + if (completer.identifierRegexps instanceof Function) { + identifierRegexps = completer.identifierRegexps(editor); + } else { + identifierRegexps = completer.identifierRegexps; + } + if (identifierRegexps && Array.isArray(identifierRegexps)) { + identifierRegexps.forEach(function(identifierRegex) { + if (!prefix && identifierRegex) + prefix = this.retrievePrecedingIdentifier(line, pos.column, identifierRegex); + }.bind(this)); + } } }.bind(this)); return prefix || this.retrievePrecedingIdentifier(line, pos.column); @@ -73,8 +81,17 @@ exports.triggerAutocomplete = function (editor, previousChar) { ? editor.session.getPrecedingCharacter() : previousChar; return editor.completers.some((completer) => { - if (completer.triggerCharacters && Array.isArray(completer.triggerCharacters)) { - return completer.triggerCharacters.includes(previousChar); + if (completer.triggerCharacters) { + var triggerCharacters; + if (completer.triggerCharacters instanceof Function) { + triggerCharacters = completer.triggerCharacters(editor); + } else { + triggerCharacters = completer.triggerCharacters; + } + + if (triggerCharacters && Array.isArray(triggerCharacters)) { + return triggerCharacters.includes(previousChar); + } } }); }; diff --git a/src/ext/language_tools.js b/src/ext/language_tools.js index cc04a46c7b8..a76748bc3d3 100644 --- a/src/ext/language_tools.js +++ b/src/ext/language_tools.js @@ -37,6 +37,16 @@ var MarkerGroup = require("../marker_group").MarkerGroup; var textCompleter = require("../autocomplete/text_completer"); /**@type {import("../../ace-internal").Ace.Completer}*/ var keyWordCompleter = { + identifierRegexps(editor) { + var completer = editor.session.$mode.completer; + if (completer) { + if (completer.identifierRegexps instanceof Function) { + return completer.identifierRegexps(editor); + } else { + return completer.identifierRegexps; + } + } + }, getCompletions: function(editor, session, pos, prefix, callback) { if (session.$mode.completer) { return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback); @@ -49,7 +59,17 @@ var keyWordCompleter = { }); callback(null, completions); }, - id: "keywordCompleter" + id: "keywordCompleter", + triggerCharacters(editor) { + var completer = editor.session.$mode.completer; + if (completer) { + if (completer.triggerCharacters instanceof Function) { + return completer.triggerCharacters(editor); + } else { + return completer.triggerCharacters; + } + } + } }; var transformSnippetTooltip = function(str) { diff --git a/src/mode/latex.js b/src/mode/latex.js index 731c83deeca..9bd4bbadbb7 100644 --- a/src/mode/latex.js +++ b/src/mode/latex.js @@ -19,6 +19,8 @@ oop.inherits(Mode, TextMode); this.lineCommentStart = "%"; this.$id = "ace/mode/latex"; + + this.snippetFileId = "ace/snippets/latex"; this.getMatching = function(session, row, column) { if (row == undefined) @@ -35,6 +37,53 @@ oop.inherits(Mode, TextMode); return this.foldingRules.latexBlock(session, row, column, true); } }; + + function wordDistances(doc, pos) { + var macroName = /\\[a-zA-Z0-9]*/g; + + var words = [...doc.getValue().matchAll(macroName)]; + var wordScores = Object.create(null); + + var textBefore = doc.getTextRange(Range.fromPoints({ + row: 0, + column: 0 + }, pos)); + var prefixPos = [...textBefore.matchAll(macroName)].length - 1; + + var words = [...doc.getValue().matchAll(macroName)]; + var wordScores = Object.create(null); + + var currentWord = words[prefixPos]; + + words.forEach(function (word, idx) { + if (!word || word === currentWord || word === "\\") return; + + var distance = Math.abs(prefixPos - idx); + var score = words.length - distance; + wordScores[word] = Math.max(score, wordScores[word] ?? 0); + }); + return wordScores; + } + + this.completer = { + identifierRegexps: [/[\\a-zA-Z0-0]/], + getCompletions: (editor, session, pos, prefix, callback) => { + var wordScores = wordDistances(session, pos); + var wordList = Object.keys(wordScores); + callback(null, wordList.map(function (word) { + return { + caption: word, + value: word, + score: wordScores[word], + meta: "macro", + completer: this, + completerId: this.id, + }; + }, this)); + }, + triggerCharacters: ["\\"], + id: "latexMacroCompleter", + }; }).call(Mode.prototype); exports.Mode = Mode; diff --git a/src/snippets/latex.js b/src/snippets/latex.js new file mode 100644 index 00000000000..bc8b6e1b1b5 --- /dev/null +++ b/src/snippets/latex.js @@ -0,0 +1,4 @@ +"use strict"; + +exports.snippetText = require("./latex.snippets"); +exports.scope = "latex"; diff --git a/src/snippets/latex.snippets.js b/src/snippets/latex.snippets.js new file mode 100644 index 00000000000..4108be97272 --- /dev/null +++ b/src/snippets/latex.snippets.js @@ -0,0 +1 @@ +module.exports = require("./tex.snippets"); \ No newline at end of file