@codemirror-autocomplete-npm-6.18.4.patch 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. diff --git a/dist/index.cjs b/dist/index.cjs
  2. index 39215ae..b44cb76 100644
  3. --- a/dist/index.cjs
  4. +++ b/dist/index.cjs
  5. @@ -187,16 +187,23 @@ Helper function that returns a transaction spec which inserts a
  6. completion's text in the main selection range, and any other
  7. selection range that has the same text in front of it.
  8. */
  9. -function insertCompletionText(state$1, text, from, to) {
  10. +function insertCompletionText(state$1, text, from, to, extend) {
  11. let { main } = state$1.selection, fromOff = from - main.from, toOff = to - main.from;
  12. return Object.assign(Object.assign({}, state$1.changeByRange(range => {
  13. if (range != main && from != to &&
  14. state$1.sliceDoc(range.from + fromOff, range.from + toOff) != state$1.sliceDoc(from, to))
  15. return { range };
  16. - let lines = state$1.toText(text);
  17. + let change = {
  18. + from: range.from + fromOff,
  19. + to: to == main.from ? range.to : range.from + toOff,
  20. + insert: text instanceof state.Text ? text : state$1.toText(text),
  21. + };
  22. + if (extend) {
  23. + extend(state$1, change);
  24. + }
  25. return {
  26. - changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
  27. - range: state.EditorSelection.cursor(range.from + fromOff + lines.length)
  28. + changes: change,
  29. + range: state.EditorSelection.cursor(change.from + change.insert.length)
  30. };
  31. })), { scrollIntoView: true, userEvent: "input.complete" });
  32. }
  33. @@ -389,7 +396,9 @@ const completionConfig = state.Facet.define({
  34. filterStrict: false,
  35. compareCompletions: (a, b) => a.label.localeCompare(b.label),
  36. interactionDelay: 75,
  37. - updateSyncTime: 100
  38. + updateSyncTime: 100,
  39. + // overleaf: default to at top which is default CM6 behaviour
  40. + unfilteredResultsAtEnd: false
  41. }, {
  42. defaultKeymap: (a, b) => a && b,
  43. closeOnBlur: (a, b) => a && b,
  44. @@ -744,6 +753,7 @@ function score(option) {
  45. (option.type ? 1 : 0);
  46. }
  47. function sortOptions(active, state) {
  48. + var _a;
  49. let options = [];
  50. let sections = null;
  51. let addOption = (option) => {
  52. @@ -763,7 +773,8 @@ function sortOptions(active, state) {
  53. let getMatch = a.result.getMatch;
  54. if (a.result.filter === false) {
  55. for (let option of a.result.options) {
  56. - addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
  57. + let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
  58. + addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
  59. }
  60. }
  61. else {
  62. @@ -790,15 +801,42 @@ function sortOptions(active, state) {
  63. }
  64. }
  65. let result = [], prev = null;
  66. + const priorityIndices = new Map();
  67. let compare = conf.compareCompletions;
  68. for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
  69. + // overleaf: Deduplicate results with dedup options
  70. + // The goal is to keep only the highest priority option, in the
  71. + // highest scoring position.
  72. + const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
  73. + if (key) {
  74. + // Handle merging specifically for deduplicated items item
  75. + const currentOptionIndex = priorityIndices.get(key);
  76. + if (currentOptionIndex === undefined) {
  77. + priorityIndices.set(key, result.length);
  78. + result.push(opt);
  79. + prev = opt.completion;
  80. + }
  81. + else {
  82. + if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
  83. + result[currentOptionIndex] = opt;
  84. + if (currentOptionIndex === result.length - 1) {
  85. + prev = opt.completion;
  86. + }
  87. + }
  88. + }
  89. + continue;
  90. + }
  91. + // overleaf: end
  92. let cur = opt.completion;
  93. - if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
  94. - (prev.type != null && cur.type != null && prev.type != cur.type) ||
  95. - prev.apply != cur.apply || prev.boost != cur.boost)
  96. + if (!prev || prev.label != cur.label)
  97. + result.push(opt);
  98. + // overleaf: we're already handling deduplication, so skip extra merges
  99. + else if (prev.deduplicate)
  100. result.push(opt);
  101. else if (score(opt.completion) > score(prev))
  102. result[result.length - 1] = opt;
  103. + else if (opt.completion.info)
  104. + result[result.length - 1] = opt;
  105. prev = opt.completion;
  106. }
  107. return result;
  108. @@ -817,8 +855,9 @@ class CompletionDialog {
  109. : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
  110. }
  111. static build(active, state, id, prev, conf, didSetActive) {
  112. - if (prev && !didSetActive && active.some(s => s.isPending))
  113. - return prev.setDisabled();
  114. + // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
  115. + // if (prev && !didSetActive && active.some(s => s.isPending))
  116. + // return prev.setDisabled()
  117. let options = sortOptions(active, state);
  118. if (!options.length)
  119. return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
  120. @@ -1017,13 +1056,14 @@ const completionState = state.StateField.define({
  121. view.EditorView.contentAttributes.from(f, state => state.attrs)
  122. ]
  123. });
  124. +const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
  125. function applyCompletion(view, option) {
  126. const apply = option.completion.apply || option.completion.label;
  127. let result = view.state.field(completionState).active.find(a => a.source == option.source);
  128. if (!(result instanceof ActiveResult))
  129. return false;
  130. if (typeof apply == "string")
  131. - view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
  132. + view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
  133. else
  134. apply(view, option.completion, result.from, result.to);
  135. return true;
  136. @@ -1559,20 +1599,42 @@ interpreted as indicating a placeholder.
  137. function snippet(template) {
  138. let snippet = Snippet.parse(template);
  139. return (editor, completion, from, to) => {
  140. - let { text, ranges } = snippet.instantiate(editor.state, from);
  141. - let { main } = editor.state.selection;
  142. - let spec = {
  143. - changes: { from, to: to == main.from ? main.to : to, insert: state.Text.of(text) },
  144. - scrollIntoView: true,
  145. - annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined
  146. - };
  147. + let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
  148. + let ranges = [];
  149. + let totalOffset = 0;
  150. + let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
  151. + if (range != main && from != to &&
  152. + editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
  153. + return { range };
  154. + let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
  155. + let change = {
  156. + from: range.from + fromOff,
  157. + to: range.from + toOff,
  158. + insert: state.Text.of(text)
  159. + };
  160. + let originalTo = change.to;
  161. + let offset = change.insert.length + fromOff;
  162. + if (completion.extend) {
  163. + completion.extend(editor.state, change);
  164. + offset += originalTo - change.to;
  165. + }
  166. + for (const fieldRange of fieldRanges) {
  167. + ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
  168. + }
  169. + totalOffset += offset;
  170. + return {
  171. + changes: change,
  172. + range: state.EditorSelection.cursor(change.from + change.insert.length)
  173. + };
  174. + })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
  175. if (ranges.length)
  176. spec.selection = fieldSelection(ranges, 0);
  177. if (ranges.some(r => r.field > 0)) {
  178. let active = new ActiveSnippet(ranges, 0);
  179. - let effects = spec.effects = [setActive.of(active)];
  180. - if (editor.state.field(snippetState, false) === undefined)
  181. - effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
  182. + spec.effects.push(setActive.of(active));
  183. + if (editor.state.field(snippetState, false) === undefined) {
  184. + spec.effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
  185. + }
  186. }
  187. editor.dispatch(editor.state.update(spec));
  188. };
  189. @@ -1746,7 +1808,8 @@ const completeAnyWord = context => {
  190. const defaults = {
  191. brackets: ["(", "[", "{", "'", '"'],
  192. before: ")]}:;>",
  193. - stringPrefixes: []
  194. + stringPrefixes: [],
  195. + buildInsert: (state, range, open, close) => open + close,
  196. };
  197. const closeBracketEffect = state.StateEffect.define({
  198. map(value, mapping) {
  199. @@ -1854,8 +1917,8 @@ function insertBracket(state$1, bracket) {
  200. for (let tok of tokens) {
  201. let closed = closing(state.codePointAt(tok, 0));
  202. if (bracket == tok)
  203. - return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
  204. - : handleOpen(state$1, tok, closed, conf.before || defaults.before);
  205. + return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
  206. + : handleOpen(state$1, tok, closed, conf.before || defaults.before, conf);
  207. if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
  208. return handleClose(state$1, tok, closed);
  209. }
  210. @@ -1877,17 +1940,21 @@ function prevChar(doc, pos) {
  211. let prev = doc.sliceString(pos - 2, pos);
  212. return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
  213. }
  214. -function handleOpen(state$1, open, close, closeBefore) {
  215. +function handleOpen(state$1, open, close, closeBefore, config) {
  216. + let buildInsert = config.buildInsert || defaults.buildInsert;
  217. let dont = null, changes = state$1.changeByRange(range => {
  218. + var _a;
  219. if (!range.empty)
  220. return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
  221. effects: closeBracketEffect.of(range.to + open.length),
  222. range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
  223. let next = nextChar(state$1.doc, range.head);
  224. - if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
  225. - return { changes: { insert: open + close, from: range.head },
  226. - effects: closeBracketEffect.of(range.head + open.length),
  227. + if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
  228. + const insert = (_a = buildInsert(state$1, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
  229. + return { changes: { insert, from: range.head },
  230. + effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
  231. range: state.EditorSelection.cursor(range.head + open.length) };
  232. + }
  233. return { range: dont = range };
  234. });
  235. return dont ? null : state$1.update(changes, {
  236. @@ -1909,18 +1976,36 @@ function handleClose(state$1, _open, close) {
  237. }
  238. // Handles cases where the open and close token are the same, and
  239. // possibly triple quotes (as in `"""abc"""`-style quoting).
  240. -function handleSame(state$1, token, allowTriple, config) {
  241. +function handleSame(state$1, token, allowDouble, allowTriple, config) {
  242. let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
  243. + let buildInsert = config.buildInsert || defaults.buildInsert;
  244. let dont = null, changes = state$1.changeByRange(range => {
  245. + var _a, _b, _c;
  246. if (!range.empty)
  247. return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
  248. effects: closeBracketEffect.of(range.to + token.length),
  249. range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
  250. let pos = range.head, next = nextChar(state$1.doc, pos), start;
  251. - if (next == token) {
  252. + if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
  253. + (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
  254. + nodeStart(state$1, start)) {
  255. + return { changes: { insert: token + token + token + token, from: pos },
  256. + effects: closeBracketEffect.of(pos + token.length),
  257. + range: state.EditorSelection.cursor(pos + token.length) };
  258. + }
  259. + else if (allowDouble && state$1.sliceDoc(pos - token.length, pos) == token &&
  260. + (start = canStartStringAt(state$1, pos - token.length, stringPrefixes)) > -1 &&
  261. + nodeStart(state$1, start)) {
  262. + let insert = (_a = buildInsert(state$1, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
  263. + return { changes: { insert, from: pos },
  264. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  265. + range: state.EditorSelection.cursor(pos + token.length) };
  266. + }
  267. + else if (next == token) {
  268. if (nodeStart(state$1, pos)) {
  269. - return { changes: { insert: token + token, from: pos },
  270. - effects: closeBracketEffect.of(pos + token.length),
  271. + let insert = (_b = buildInsert(state$1, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
  272. + return { changes: { insert, from: pos },
  273. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  274. range: state.EditorSelection.cursor(pos + token.length) };
  275. }
  276. else if (closedBracketAt(state$1, pos)) {
  277. @@ -1930,18 +2015,13 @@ function handleSame(state$1, token, allowTriple, config) {
  278. range: state.EditorSelection.cursor(pos + content.length) };
  279. }
  280. }
  281. - else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
  282. - (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
  283. - nodeStart(state$1, start)) {
  284. - return { changes: { insert: token + token + token + token, from: pos },
  285. - effects: closeBracketEffect.of(pos + token.length),
  286. - range: state.EditorSelection.cursor(pos + token.length) };
  287. - }
  288. else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
  289. - if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
  290. - return { changes: { insert: token + token, from: pos },
  291. - effects: closeBracketEffect.of(pos + token.length),
  292. + if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes)) {
  293. + const insert = (_c = buildInsert(state$1, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
  294. + return { changes: { insert, from: pos },
  295. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  296. range: state.EditorSelection.cursor(pos + token.length) };
  297. + }
  298. }
  299. return { range: dont = range };
  300. });
  301. @@ -2086,6 +2166,7 @@ exports.completionKeymap = completionKeymap;
  302. exports.completionStatus = completionStatus;
  303. exports.currentCompletions = currentCompletions;
  304. exports.deleteBracketPair = deleteBracketPair;
  305. +exports.getCompletionTooltip = getCompletionTooltip;
  306. exports.hasNextSnippetField = hasNextSnippetField;
  307. exports.hasPrevSnippetField = hasPrevSnippetField;
  308. exports.ifIn = ifIn;
  309. @@ -2093,8 +2174,10 @@ exports.ifNotIn = ifNotIn;
  310. exports.insertBracket = insertBracket;
  311. exports.insertCompletionText = insertCompletionText;
  312. exports.moveCompletionSelection = moveCompletionSelection;
  313. +exports.nextChar = nextChar;
  314. exports.nextSnippetField = nextSnippetField;
  315. exports.pickedCompletion = pickedCompletion;
  316. +exports.prevChar = prevChar;
  317. exports.prevSnippetField = prevSnippetField;
  318. exports.selectedCompletion = selectedCompletion;
  319. exports.selectedCompletionIndex = selectedCompletionIndex;
  320. diff --git a/dist/index.d.cts b/dist/index.d.cts
  321. index b57b8f6..fce47ab 100644
  322. --- a/dist/index.d.cts
  323. +++ b/dist/index.d.cts
  324. @@ -1,6 +1,6 @@
  325. import * as _codemirror_state from '@codemirror/state';
  326. -import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
  327. -import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
  328. +import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
  329. +import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
  330. import * as _lezer_common from '@lezer/common';
  331. /**
  332. @@ -73,6 +73,19 @@ interface Completion {
  333. a `{name}` object.
  334. */
  335. section?: string | CompletionSection;
  336. + /**
  337. + Can be used to alter the change created when the completion is applied
  338. + */
  339. + extend?: ExtendCompletion;
  340. + /**
  341. + If multiple sources return the same result, use this field to specifiy a
  342. + deduplication key as well as a priority. For each unique key, only the
  343. + completion with the highest priority will be shown.
  344. + */
  345. + deduplicate?: {
  346. + key: string;
  347. + priority: number;
  348. + };
  349. }
  350. /**
  351. The type returned from
  352. @@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
  353. picking a completion.
  354. */
  355. declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
  356. +type ExtendCompletion = (state: EditorState, change: {
  357. + from: number;
  358. + to: number;
  359. + insert: string | Text;
  360. +}) => void;
  361. /**
  362. Helper function that returns a transaction spec which inserts a
  363. completion's text in the main selection range, and any other
  364. selection range that has the same text in front of it.
  365. */
  366. -declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
  367. +declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
  368. interface CompletionConfig {
  369. /**
  370. @@ -441,6 +459,10 @@ interface CompletionConfig {
  371. milliseconds.
  372. */
  373. updateSyncTime?: number;
  374. + /**
  375. + overleaf: Move unfiltered results after the filtered ones
  376. + */
  377. + unfilteredResultsAtEnd?: boolean;
  378. }
  379. /**
  380. @@ -514,6 +536,8 @@ applies the snippet.
  381. */
  382. declare function snippetCompletion(template: string, completion: Completion): Completion;
  383. +declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
  384. +
  385. /**
  386. Returns a command that moves the completion selection forward or
  387. backward by the given amount.
  388. @@ -562,6 +586,11 @@ interface CloseBracketConfig {
  389. these prefixes before the opening quote.
  390. */
  391. stringPrefixes?: string[];
  392. + /**
  393. + An optional callback for overriding the content that's inserted
  394. + based on surrounding characters
  395. + */
  396. + buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
  397. }
  398. /**
  399. Extension to enable bracket-closing behavior. When a closeable
  400. @@ -593,6 +622,8 @@ to programmatically insert brackets—the
  401. take care of running this for user input.)
  402. */
  403. declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
  404. +declare function nextChar(doc: Text, pos: number): string;
  405. +declare function prevChar(doc: Text, pos: number): string;
  406. /**
  407. Returns an extension that enables autocompletion.
  408. @@ -636,4 +667,5 @@ the currently selected completion.
  409. */
  410. declare function setSelectedCompletion(index: number): StateEffect<unknown>;
  411. -export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
  412. +export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
  413. +export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
  414. diff --git a/dist/index.d.ts b/dist/index.d.ts
  415. index b57b8f6..fce47ab 100644
  416. --- a/dist/index.d.ts
  417. +++ b/dist/index.d.ts
  418. @@ -1,6 +1,6 @@
  419. import * as _codemirror_state from '@codemirror/state';
  420. -import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
  421. -import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
  422. +import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
  423. +import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
  424. import * as _lezer_common from '@lezer/common';
  425. /**
  426. @@ -73,6 +73,19 @@ interface Completion {
  427. a `{name}` object.
  428. */
  429. section?: string | CompletionSection;
  430. + /**
  431. + Can be used to alter the change created when the completion is applied
  432. + */
  433. + extend?: ExtendCompletion;
  434. + /**
  435. + If multiple sources return the same result, use this field to specifiy a
  436. + deduplication key as well as a priority. For each unique key, only the
  437. + completion with the highest priority will be shown.
  438. + */
  439. + deduplicate?: {
  440. + key: string;
  441. + priority: number;
  442. + };
  443. }
  444. /**
  445. The type returned from
  446. @@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
  447. picking a completion.
  448. */
  449. declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
  450. +type ExtendCompletion = (state: EditorState, change: {
  451. + from: number;
  452. + to: number;
  453. + insert: string | Text;
  454. +}) => void;
  455. /**
  456. Helper function that returns a transaction spec which inserts a
  457. completion's text in the main selection range, and any other
  458. selection range that has the same text in front of it.
  459. */
  460. -declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
  461. +declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
  462. interface CompletionConfig {
  463. /**
  464. @@ -441,6 +459,10 @@ interface CompletionConfig {
  465. milliseconds.
  466. */
  467. updateSyncTime?: number;
  468. + /**
  469. + overleaf: Move unfiltered results after the filtered ones
  470. + */
  471. + unfilteredResultsAtEnd?: boolean;
  472. }
  473. /**
  474. @@ -514,6 +536,8 @@ applies the snippet.
  475. */
  476. declare function snippetCompletion(template: string, completion: Completion): Completion;
  477. +declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
  478. +
  479. /**
  480. Returns a command that moves the completion selection forward or
  481. backward by the given amount.
  482. @@ -562,6 +586,11 @@ interface CloseBracketConfig {
  483. these prefixes before the opening quote.
  484. */
  485. stringPrefixes?: string[];
  486. + /**
  487. + An optional callback for overriding the content that's inserted
  488. + based on surrounding characters
  489. + */
  490. + buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
  491. }
  492. /**
  493. Extension to enable bracket-closing behavior. When a closeable
  494. @@ -593,6 +622,8 @@ to programmatically insert brackets—the
  495. take care of running this for user input.)
  496. */
  497. declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
  498. +declare function nextChar(doc: Text, pos: number): string;
  499. +declare function prevChar(doc: Text, pos: number): string;
  500. /**
  501. Returns an extension that enables autocompletion.
  502. @@ -636,4 +667,5 @@ the currently selected completion.
  503. */
  504. declare function setSelectedCompletion(index: number): StateEffect<unknown>;
  505. -export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
  506. +export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
  507. +export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
  508. diff --git a/dist/index.js b/dist/index.js
  509. index 4729223..9361a53 100644
  510. --- a/dist/index.js
  511. +++ b/dist/index.js
  512. @@ -1,4 +1,4 @@
  513. -import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
  514. +import { Annotation, StateEffect, Text, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
  515. import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
  516. import { syntaxTree, indentUnit } from '@codemirror/language';
  517. @@ -185,16 +185,23 @@ Helper function that returns a transaction spec which inserts a
  518. completion's text in the main selection range, and any other
  519. selection range that has the same text in front of it.
  520. */
  521. -function insertCompletionText(state, text, from, to) {
  522. +function insertCompletionText(state, text, from, to, extend) {
  523. let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
  524. return Object.assign(Object.assign({}, state.changeByRange(range => {
  525. if (range != main && from != to &&
  526. state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
  527. return { range };
  528. - let lines = state.toText(text);
  529. + let change = {
  530. + from: range.from + fromOff,
  531. + to: to == main.from ? range.to : range.from + toOff,
  532. + insert: text instanceof Text ? text : state.toText(text),
  533. + };
  534. + if (extend) {
  535. + extend(state, change);
  536. + }
  537. return {
  538. - changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
  539. - range: EditorSelection.cursor(range.from + fromOff + lines.length)
  540. + changes: change,
  541. + range: EditorSelection.cursor(change.from + change.insert.length)
  542. };
  543. })), { scrollIntoView: true, userEvent: "input.complete" });
  544. }
  545. @@ -387,7 +394,9 @@ const completionConfig = /*@__PURE__*/Facet.define({
  546. filterStrict: false,
  547. compareCompletions: (a, b) => a.label.localeCompare(b.label),
  548. interactionDelay: 75,
  549. - updateSyncTime: 100
  550. + updateSyncTime: 100,
  551. + // overleaf: default to at top which is default CM6 behaviour
  552. + unfilteredResultsAtEnd: false
  553. }, {
  554. defaultKeymap: (a, b) => a && b,
  555. closeOnBlur: (a, b) => a && b,
  556. @@ -742,6 +751,7 @@ function score(option) {
  557. (option.type ? 1 : 0);
  558. }
  559. function sortOptions(active, state) {
  560. + var _a;
  561. let options = [];
  562. let sections = null;
  563. let addOption = (option) => {
  564. @@ -761,7 +771,8 @@ function sortOptions(active, state) {
  565. let getMatch = a.result.getMatch;
  566. if (a.result.filter === false) {
  567. for (let option of a.result.options) {
  568. - addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
  569. + let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
  570. + addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
  571. }
  572. }
  573. else {
  574. @@ -788,15 +799,42 @@ function sortOptions(active, state) {
  575. }
  576. }
  577. let result = [], prev = null;
  578. + const priorityIndices = new Map();
  579. let compare = conf.compareCompletions;
  580. for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
  581. + // overleaf: Deduplicate results with dedup options
  582. + // The goal is to keep only the highest priority option, in the
  583. + // highest scoring position.
  584. + const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
  585. + if (key) {
  586. + // Handle merging specifically for deduplicated items item
  587. + const currentOptionIndex = priorityIndices.get(key);
  588. + if (currentOptionIndex === undefined) {
  589. + priorityIndices.set(key, result.length);
  590. + result.push(opt);
  591. + prev = opt.completion;
  592. + }
  593. + else {
  594. + if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
  595. + result[currentOptionIndex] = opt;
  596. + if (currentOptionIndex === result.length - 1) {
  597. + prev = opt.completion;
  598. + }
  599. + }
  600. + }
  601. + continue;
  602. + }
  603. + // overleaf: end
  604. let cur = opt.completion;
  605. - if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
  606. - (prev.type != null && cur.type != null && prev.type != cur.type) ||
  607. - prev.apply != cur.apply || prev.boost != cur.boost)
  608. + if (!prev || prev.label != cur.label)
  609. + result.push(opt);
  610. + // overleaf: we're already handling deduplication, so skip extra merges
  611. + else if (prev.deduplicate)
  612. result.push(opt);
  613. else if (score(opt.completion) > score(prev))
  614. result[result.length - 1] = opt;
  615. + else if (opt.completion.info)
  616. + result[result.length - 1] = opt;
  617. prev = opt.completion;
  618. }
  619. return result;
  620. @@ -815,8 +853,9 @@ class CompletionDialog {
  621. : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
  622. }
  623. static build(active, state, id, prev, conf, didSetActive) {
  624. - if (prev && !didSetActive && active.some(s => s.isPending))
  625. - return prev.setDisabled();
  626. + // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
  627. + // if (prev && !didSetActive && active.some(s => s.isPending))
  628. + // return prev.setDisabled()
  629. let options = sortOptions(active, state);
  630. if (!options.length)
  631. return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
  632. @@ -1015,13 +1054,14 @@ const completionState = /*@__PURE__*/StateField.define({
  633. EditorView.contentAttributes.from(f, state => state.attrs)
  634. ]
  635. });
  636. +const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
  637. function applyCompletion(view, option) {
  638. const apply = option.completion.apply || option.completion.label;
  639. let result = view.state.field(completionState).active.find(a => a.source == option.source);
  640. if (!(result instanceof ActiveResult))
  641. return false;
  642. if (typeof apply == "string")
  643. - view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
  644. + view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
  645. else
  646. apply(view, option.completion, result.from, result.to);
  647. return true;
  648. @@ -1557,20 +1597,42 @@ interpreted as indicating a placeholder.
  649. function snippet(template) {
  650. let snippet = Snippet.parse(template);
  651. return (editor, completion, from, to) => {
  652. - let { text, ranges } = snippet.instantiate(editor.state, from);
  653. - let { main } = editor.state.selection;
  654. - let spec = {
  655. - changes: { from, to: to == main.from ? main.to : to, insert: Text.of(text) },
  656. - scrollIntoView: true,
  657. - annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
  658. - };
  659. + let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
  660. + let ranges = [];
  661. + let totalOffset = 0;
  662. + let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
  663. + if (range != main && from != to &&
  664. + editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
  665. + return { range };
  666. + let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
  667. + let change = {
  668. + from: range.from + fromOff,
  669. + to: range.from + toOff,
  670. + insert: Text.of(text)
  671. + };
  672. + let originalTo = change.to;
  673. + let offset = change.insert.length + fromOff;
  674. + if (completion.extend) {
  675. + completion.extend(editor.state, change);
  676. + offset += originalTo - change.to;
  677. + }
  678. + for (const fieldRange of fieldRanges) {
  679. + ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
  680. + }
  681. + totalOffset += offset;
  682. + return {
  683. + changes: change,
  684. + range: EditorSelection.cursor(change.from + change.insert.length)
  685. + };
  686. + })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
  687. if (ranges.length)
  688. spec.selection = fieldSelection(ranges, 0);
  689. if (ranges.some(r => r.field > 0)) {
  690. let active = new ActiveSnippet(ranges, 0);
  691. - let effects = spec.effects = [setActive.of(active)];
  692. - if (editor.state.field(snippetState, false) === undefined)
  693. - effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
  694. + spec.effects.push(setActive.of(active));
  695. + if (editor.state.field(snippetState, false) === undefined) {
  696. + spec.effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
  697. + }
  698. }
  699. editor.dispatch(editor.state.update(spec));
  700. };
  701. @@ -1744,7 +1806,8 @@ const completeAnyWord = context => {
  702. const defaults = {
  703. brackets: ["(", "[", "{", "'", '"'],
  704. before: ")]}:;>",
  705. - stringPrefixes: []
  706. + stringPrefixes: [],
  707. + buildInsert: (state, range, open, close) => open + close,
  708. };
  709. const closeBracketEffect = /*@__PURE__*/StateEffect.define({
  710. map(value, mapping) {
  711. @@ -1852,8 +1915,8 @@ function insertBracket(state, bracket) {
  712. for (let tok of tokens) {
  713. let closed = closing(codePointAt(tok, 0));
  714. if (bracket == tok)
  715. - return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
  716. - : handleOpen(state, tok, closed, conf.before || defaults.before);
  717. + return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
  718. + : handleOpen(state, tok, closed, conf.before || defaults.before, conf);
  719. if (bracket == closed && closedBracketAt(state, state.selection.main.from))
  720. return handleClose(state, tok, closed);
  721. }
  722. @@ -1875,17 +1938,21 @@ function prevChar(doc, pos) {
  723. let prev = doc.sliceString(pos - 2, pos);
  724. return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
  725. }
  726. -function handleOpen(state, open, close, closeBefore) {
  727. +function handleOpen(state, open, close, closeBefore, config) {
  728. + let buildInsert = config.buildInsert || defaults.buildInsert;
  729. let dont = null, changes = state.changeByRange(range => {
  730. + var _a;
  731. if (!range.empty)
  732. return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
  733. effects: closeBracketEffect.of(range.to + open.length),
  734. range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
  735. let next = nextChar(state.doc, range.head);
  736. - if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
  737. - return { changes: { insert: open + close, from: range.head },
  738. - effects: closeBracketEffect.of(range.head + open.length),
  739. + if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
  740. + const insert = (_a = buildInsert(state, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
  741. + return { changes: { insert, from: range.head },
  742. + effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
  743. range: EditorSelection.cursor(range.head + open.length) };
  744. + }
  745. return { range: dont = range };
  746. });
  747. return dont ? null : state.update(changes, {
  748. @@ -1907,18 +1974,36 @@ function handleClose(state, _open, close) {
  749. }
  750. // Handles cases where the open and close token are the same, and
  751. // possibly triple quotes (as in `"""abc"""`-style quoting).
  752. -function handleSame(state, token, allowTriple, config) {
  753. +function handleSame(state, token, allowDouble, allowTriple, config) {
  754. let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
  755. + let buildInsert = config.buildInsert || defaults.buildInsert;
  756. let dont = null, changes = state.changeByRange(range => {
  757. + var _a, _b, _c;
  758. if (!range.empty)
  759. return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
  760. effects: closeBracketEffect.of(range.to + token.length),
  761. range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
  762. let pos = range.head, next = nextChar(state.doc, pos), start;
  763. - if (next == token) {
  764. + if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
  765. + (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
  766. + nodeStart(state, start)) {
  767. + return { changes: { insert: token + token + token + token, from: pos },
  768. + effects: closeBracketEffect.of(pos + token.length),
  769. + range: EditorSelection.cursor(pos + token.length) };
  770. + }
  771. + else if (allowDouble && state.sliceDoc(pos - token.length, pos) == token &&
  772. + (start = canStartStringAt(state, pos - token.length, stringPrefixes)) > -1 &&
  773. + nodeStart(state, start)) {
  774. + let insert = (_a = buildInsert(state, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
  775. + return { changes: { insert, from: pos },
  776. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  777. + range: EditorSelection.cursor(pos + token.length) };
  778. + }
  779. + else if (next == token) {
  780. if (nodeStart(state, pos)) {
  781. - return { changes: { insert: token + token, from: pos },
  782. - effects: closeBracketEffect.of(pos + token.length),
  783. + let insert = (_b = buildInsert(state, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
  784. + return { changes: { insert, from: pos },
  785. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  786. range: EditorSelection.cursor(pos + token.length) };
  787. }
  788. else if (closedBracketAt(state, pos)) {
  789. @@ -1928,18 +2013,13 @@ function handleSame(state, token, allowTriple, config) {
  790. range: EditorSelection.cursor(pos + content.length) };
  791. }
  792. }
  793. - else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
  794. - (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
  795. - nodeStart(state, start)) {
  796. - return { changes: { insert: token + token + token + token, from: pos },
  797. - effects: closeBracketEffect.of(pos + token.length),
  798. - range: EditorSelection.cursor(pos + token.length) };
  799. - }
  800. else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
  801. - if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
  802. - return { changes: { insert: token + token, from: pos },
  803. - effects: closeBracketEffect.of(pos + token.length),
  804. + if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes)) {
  805. + const insert = (_c = buildInsert(state, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
  806. + return { changes: { insert, from: pos },
  807. + effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
  808. range: EditorSelection.cursor(pos + token.length) };
  809. + }
  810. }
  811. return { range: dont = range };
  812. });
  813. @@ -2071,4 +2151,4 @@ function setSelectedCompletion(index) {
  814. return setSelectedEffect.of(index);
  815. }
  816. -export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
  817. +export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };