Skip to content

Commit 3201e38

Browse files
authored
Merge pull request #2 from overleaf/mj-upgrade-6-18-0
Upgrade to codemirror/autocomplete 6.18.0
2 parents 2aff95f + d69a03a commit 3201e38

File tree

11 files changed

+242
-67
lines changed

11 files changed

+242
-67
lines changed

CHANGELOG.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,65 @@
1+
## 6.18.0 (2024-08-05)
2+
3+
### Bug fixes
4+
5+
Style the info element so that newlines are preserved, to make it easier to display multi-line info from a string source.
6+
7+
### New features
8+
9+
When registering an `abort` handler for a completion query, you can now use the `onDocChange` option to indicate that your query should be aborted as soon as the document changes while it is running.
10+
11+
## 6.17.0 (2024-07-03)
12+
13+
### Bug fixes
14+
15+
Fix an issue where completions weren't properly reset when starting a new completion through `activateOnCompletion`.
16+
17+
### New features
18+
19+
`CompletionContext` objects now have a `view` property that holds the editor view when the query context has a view available.
20+
21+
## 6.16.3 (2024-06-19)
22+
23+
### Bug fixes
24+
25+
Avoid adding an `aria-autocomplete` attribute to the editor when there are no active sources active.
26+
27+
## 6.16.2 (2024-05-31)
28+
29+
### Bug fixes
30+
31+
Allow backslash-escaped closing braces inside snippet field names/content.
32+
33+
## 6.16.1 (2024-05-29)
34+
35+
### Bug fixes
36+
37+
Fix a bug where multiple backslashes before a brace in a snippet were all removed.
38+
39+
## 6.16.0 (2024-04-12)
40+
41+
### New features
42+
43+
The new `activateOnCompletion` option allows autocompletion to be configured to chain completion activation for some types of completions.
44+
45+
## 6.15.0 (2024-03-13)
46+
47+
### New features
48+
49+
The new `filterStrict` option can be used to turn off fuzzy matching of completions.
50+
51+
## 6.14.0 (2024-03-10)
52+
53+
### New features
54+
55+
Completion results can now define a `map` method that can be used to adjust position-dependent information for document changes.
56+
57+
## 6.13.0 (2024-02-29)
58+
59+
### New features
60+
61+
Completions may now provide 'commit characters' that, when typed, commit the completion before inserting the character.
62+
163
## 6.12.0 (2024-01-12)
264

365
### Bug fixes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codemirror/autocomplete",
3-
"version": "6.12.0",
3+
"version": "6.18.0",
44
"description": "Autocompletion for the CodeMirror code editor",
55
"scripts": {
66
"test": "cm-runtests",

src/closebrackets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {syntaxTree} from "@codemirror/language"
1111
export interface CloseBracketConfig {
1212
/// The opening brackets to close. Defaults to `["(", "[", "{", "'",
1313
/// '"']`. Brackets may be single characters or a triple of quotes
14-
/// (as in `"''''"`).
14+
/// (as in `"'''"`).
1515
brackets?: string[]
1616
/// Characters in front of which newly opened brackets are
1717
/// automatically closed. Closing always happens in front of

src/completion.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {EditorView} from "@codemirror/view"
2-
import {EditorState, StateEffect, Annotation, EditorSelection, TransactionSpec, Text} from "@codemirror/state"
2+
import {EditorState, StateEffect, Annotation, EditorSelection, TransactionSpec, Text, ChangeDesc} from "@codemirror/state"
33
import {syntaxTree} from "@codemirror/language"
44
import {SyntaxNode} from "@lezer/common"
55

@@ -39,6 +39,9 @@ export interface Completion {
3939
///
4040
/// Multiple types can be provided by separating them with spaces.
4141
type?: string
42+
/// When this option is selected, and one of these characters is
43+
/// typed, insert the completion before typing the character.
44+
commitCharacters?: readonly string[],
4245
/// When given, should be a number from -99 to 99 that adjusts how
4346
/// this completion is ranked compared to other completions that
4447
/// match the input as well as this one. A negative number moves it
@@ -86,6 +89,8 @@ export interface CompletionSection {
8689
export class CompletionContext {
8790
/// @internal
8891
abortListeners: (() => void)[] | null = []
92+
/// @internal
93+
abortOnDocChange = false
8994

9095
/// Create a new completion context. (Mostly useful for testing
9196
/// completion sources—in the editor, the extension will create
@@ -99,7 +104,13 @@ export class CompletionContext {
99104
/// implicitly by typing. The usual way to respond to this is to
100105
/// only return completions when either there is part of a
101106
/// completable entity before the cursor, or `explicit` is true.
102-
readonly explicit: boolean
107+
readonly explicit: boolean,
108+
/// The editor view. May be undefined if the context was created
109+
/// in a situation where there is no such view available, such as
110+
/// in synchronous updates via
111+
/// [`CompletionResult.update`](#autocomplete.CompletionResult.update)
112+
/// or when called by test code.
113+
readonly view?: EditorView
103114
) {}
104115

105116
/// Get the extent, content, and (if there is a token) type of the
@@ -129,8 +140,19 @@ export class CompletionContext {
129140
/// Allows you to register abort handlers, which will be called when
130141
/// the query is
131142
/// [aborted](#autocomplete.CompletionContext.aborted).
132-
addEventListener(type: "abort", listener: () => void) {
133-
if (type == "abort" && this.abortListeners) this.abortListeners.push(listener)
143+
///
144+
/// By default, running queries will not be aborted for regular
145+
/// typing or backspacing, on the assumption that they are likely to
146+
/// return a result with a
147+
/// [`validFor`](#autocomplete.CompletionResult.validFor) field that
148+
/// allows the result to be used after all. Passing `onDocChange:
149+
/// true` will cause this query to be aborted for any document
150+
/// change.
151+
addEventListener(type: "abort", listener: () => void, options?: {onDocChange: boolean}) {
152+
if (type == "abort" && this.abortListeners) {
153+
this.abortListeners.push(listener)
154+
if (options && options.onDocChange) this.abortOnDocChange = true
155+
}
134156
}
135157
}
136158

@@ -235,6 +257,16 @@ export interface CompletionResult {
235257
/// [`validFor`](#autocomplete.CompletionResult.validFor)) that the
236258
/// completion still applies in the new state.
237259
update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null
260+
/// When results contain position-dependent information in, for
261+
/// example, `apply` methods, you can provide this method to update
262+
/// the result for transactions that happen after the query. It is
263+
/// not necessary to update `from` and `to`—those are tracked
264+
/// automatically.
265+
map?: (current: CompletionResult, changes: ChangeDesc) => CompletionResult | null
266+
/// Set a default set of [commit
267+
/// characters](#autocomplete.Completion.commitCharacters) for all
268+
/// options in this result.
269+
commitCharacters?: readonly string[]
238270
}
239271

240272
export class Option {

src/config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export interface CompletionConfig {
77
/// When enabled (defaults to true), autocompletion will start
88
/// whenever the user types something that can be completed.
99
activateOnTyping?: boolean
10+
/// When given, if a completion that matches the predicate is
11+
/// picked, reactivate completion again as if it was typed normally.
12+
activateOnCompletion?: (completion: Completion) => boolean
1013
/// The amount of time to wait for further typing before querying
1114
/// completion sources via
1215
/// [`activateOnTyping`](#autocomplete.autocompletion^config.activateOnTyping).
@@ -72,6 +75,11 @@ export interface CompletionConfig {
7275
/// match score. Defaults to using
7376
/// [`localeCompare`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare).
7477
compareCompletions?: (a: Completion, b: Completion) => number
78+
/// When set to true (the default is false), turn off fuzzy matching
79+
/// of completions and only show those that start with the text the
80+
/// user typed. Only takes effect for results where
81+
/// [`filter`](#autocomplete.CompletionResult.filter) isn't false.
82+
filterStrict?: boolean
7583
/// By default, commands relating to an open completion only take
7684
/// effect 75 milliseconds after the completion opened, so that key
7785
/// presses made before the user is aware of the tooltip don't go to
@@ -88,6 +96,7 @@ export const completionConfig = Facet.define<CompletionConfig, Required<Completi
8896
combine(configs) {
8997
return combineConfig<Required<CompletionConfig>>(configs, {
9098
activateOnTyping: true,
99+
activateOnCompletion: () => false,
91100
activateOnTypingDelay: 100,
92101
selectOnOpen: true,
93102
override: null,
@@ -100,6 +109,7 @@ export const completionConfig = Facet.define<CompletionConfig, Required<Completi
100109
icons: true,
101110
addToOptions: [],
102111
positionInfo: defaultPositionInfo as any,
112+
filterStrict: false,
103113
compareCompletions: (a, b) => a.label.localeCompare(b.label),
104114
interactionDelay: 75,
105115
updateSyncTime: 100
@@ -109,7 +119,8 @@ export const completionConfig = Facet.define<CompletionConfig, Required<Completi
109119
icons: (a, b) => a && b,
110120
tooltipClass: (a, b) => c => joinClass(a(c), b(c)),
111121
optionClass: (a, b) => c => joinClass(a(c), b(c)),
112-
addToOptions: (a, b) => a.concat(b)
122+
addToOptions: (a, b) => a.concat(b),
123+
filterStrict: (a, b) => a || b,
113124
})
114125
}
115126
})

src/filter.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class FuzzyMatcher {
4343
ret(score: number, matched: readonly number[]) {
4444
this.score = score
4545
this.matched = matched
46-
return true
46+
return this
4747
}
4848

4949
// Matches a given word (completion) against the pattern (input).
@@ -53,9 +53,9 @@ export class FuzzyMatcher {
5353
//
5454
// The score is a number that is more negative the worse the match
5555
// is. See `Penalty` above.
56-
match(word: string): boolean {
56+
match(word: string): {score: number, matched: readonly number[]} | null {
5757
if (this.pattern.length == 0) return this.ret(Penalty.NotFull, [])
58-
if (word.length < this.pattern.length) return false
58+
if (word.length < this.pattern.length) return null
5959
let {chars, folded, any, precise, byWord} = this
6060
// For single-character queries, only match when they occur right
6161
// at the start
@@ -64,7 +64,7 @@ export class FuzzyMatcher {
6464
let score = firstSize == word.length ? 0 : Penalty.NotFull
6565
if (first == chars[0]) {}
6666
else if (first == folded[0]) score += Penalty.CaseFold
67-
else return false
67+
else return null
6868
return this.ret(score, [0, firstSize])
6969
}
7070
let direct = word.indexOf(this.pattern)
@@ -78,7 +78,7 @@ export class FuzzyMatcher {
7878
i += codePointSize(next)
7979
}
8080
// No match, exit immediately
81-
if (anyTo < len) return false
81+
if (anyTo < len) return null
8282
}
8383

8484
// This tracks the extent of the precise (non-folded, not
@@ -129,7 +129,7 @@ export class FuzzyMatcher {
129129
if (byWordTo == len)
130130
return this.result(Penalty.ByWord + (byWordFolded ? Penalty.CaseFold : 0) + Penalty.NotStart +
131131
(wordAdjacent ? 0 : Penalty.Gap), byWord, word)
132-
return chars.length == 2 ? false
132+
return chars.length == 2 ? null
133133
: this.result((any[0] ? Penalty.NotStart : 0) + Penalty.CaseFold + Penalty.Gap, any, word)
134134
}
135135

@@ -143,3 +143,24 @@ export class FuzzyMatcher {
143143
return this.ret(score - word.length, result)
144144
}
145145
}
146+
147+
148+
export class StrictMatcher {
149+
matched: readonly number[] = []
150+
score: number = 0
151+
folded: string
152+
153+
constructor(readonly pattern: string) {
154+
this.folded = pattern.toLowerCase()
155+
}
156+
157+
match(word: string): {score: number, matched: readonly number[]} | null {
158+
if (word.length < this.pattern.length) return null
159+
let start = word.slice(0, this.pattern.length)
160+
let match = start == this.pattern ? 0 : start.toLowerCase() == this.folded ? Penalty.CaseFold : null
161+
if (match == null) return null
162+
this.matched = [0, start.length]
163+
this.score = match + (word.length == this.pattern.length ? 0 : Penalty.NotFull)
164+
return this
165+
}
166+
}

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {keymap, KeyBinding} from "@codemirror/view"
33
import {Completion, Option} from "./completion"
44
import {completionState, State, setSelectedEffect} from "./state"
55
import {CompletionConfig, completionConfig} from "./config"
6-
import {completionPlugin, moveCompletionSelection, acceptCompletion, startCompletion, closeCompletion} from "./view"
6+
import {completionPlugin, moveCompletionSelection, acceptCompletion,
7+
startCompletion, closeCompletion, commitCharacters} from "./view"
78
import {baseTheme} from "./theme"
89

910
export {snippet, snippetCompletion, nextSnippetField, prevSnippetField,
@@ -18,6 +19,7 @@ export {getCompletionTooltip} from "./state"
1819
/// Returns an extension that enables autocompletion.
1920
export function autocompletion(config: CompletionConfig = {}): Extension {
2021
return [
22+
commitCharacters,
2123
completionState,
2224
completionConfig.of(config),
2325
completionPlugin,

src/snippet.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ class Snippet {
4646

4747
static parse(template: string) {
4848
let fields: {seq: number | null, name: string}[] = []
49-
let lines = [], positions = [], m
49+
let lines = [], positions: FieldPos[] = [], m
5050
for (let line of template.split(/\r\n?|\n/)) {
51-
while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(line)) {
52-
let seq = m[1] ? +m[1] : null, name = m[2] || m[3] || "", found = -1
51+
while (m = /[#$]\{(?:(\d+)(?::([^}]*))?|((?:\\[{}]|[^}])*))\}/.exec(line)) {
52+
let seq = m[1] ? +m[1] : null, rawName = m[2] || m[3] || "", found = -1
53+
let name = rawName.replace(/\\[{}]/g, m => m[1])
5354
for (let i = 0; i < fields.length; i++) {
5455
if (seq != null ? fields[i].seq == seq : name ? fields[i].name == name : false) found = i
5556
}
@@ -61,15 +62,15 @@ class Snippet {
6162
for (let pos of positions) if (pos.field >= found) pos.field++
6263
}
6364
positions.push(new FieldPos(found, lines.length, m.index, m.index + name.length))
64-
line = line.slice(0, m.index) + name + line.slice(m.index + m[0].length)
65+
line = line.slice(0, m.index) + rawName + line.slice(m.index + m[0].length)
6566
}
66-
for (let esc; esc = /\\([{}])/.exec(line);) {
67-
line = line.slice(0, esc.index) + esc[1] + line.slice(esc.index + esc[0].length)
68-
for (let pos of positions) if (pos.line == lines.length && pos.from > esc.index) {
67+
line = line.replace(/\\([{}])/g, (_, brace, index) => {
68+
for (let pos of positions) if (pos.line == lines.length && pos.from > index) {
6969
pos.from--
7070
pos.to--
7171
}
72-
}
72+
return brace
73+
})
7374
lines.push(line)
7475
}
7576
return new Snippet(lines, positions)

0 commit comments

Comments
 (0)