Skip to content

Commit 51ba367

Browse files
Merge remote-tracking branch 'upstream/main' into main-overleaf
2 parents c562535 + fb1c899 commit 51ba367

File tree

8 files changed

+58
-16
lines changed

8 files changed

+58
-16
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
## 6.12.0 (2024-01-12)
2+
3+
### Bug fixes
4+
5+
Make sure snippet completions also set `userEvent` to `input.complete`.
6+
7+
Fix a crash when the editor lost focus during an update and autocompletion was active.
8+
9+
Fix a crash when using a snippet that has only one field, but multiple instances of that field.
10+
11+
### New features
12+
13+
The new `activateOnTypingDelay` option allows control over the debounce time before the completions are queried when the user types.
14+
15+
## 6.11.1 (2023-11-27)
16+
17+
### Bug fixes
18+
19+
Fix a bug that caused typing over closed brackets after pressing enter to still not work in many situations.
20+
21+
## 6.11.0 (2023-11-09)
22+
23+
### Bug fixes
24+
25+
Fix an issue that would prevent typing over closed brackets after starting a new line with enter.
26+
27+
### New features
28+
29+
Additional elements rendered in completion options with `addToOptions` are now given access to the editor view.
30+
131
## 6.10.2 (2023-10-13)
232

333
### 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.10.2",
3+
"version": "6.12.0",
44
"description": "Autocompletion for the CodeMirror code editor",
55
"scripts": {
66
"test": "cm-runtests",

src/closebrackets.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,11 @@ closedBracket.startSide = 1; closedBracket.endSide = -1
5151
const bracketState = StateField.define<RangeSet<typeof closedBracket>>({
5252
create() { return RangeSet.empty },
5353
update(value, tr) {
54+
value = value.map(tr.changes)
5455
if (tr.selection) {
55-
let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from
56-
let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from
57-
if (lineStart != tr.changes.mapPos(prevLineStart, -1))
58-
value = RangeSet.empty
56+
let line = tr.state.doc.lineAt(tr.selection.main.head)
57+
value = value.update({filter: from => from >= line.from && from <= line.to})
5958
}
60-
value = value.map(tr.changes)
6159
for (let effect of tr.effects) if (effect.is(closeBracketEffect))
6260
value = value.update({add: [closedBracket.range(effect.value, effect.value + 1)]})
6361
return value

src/config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ 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+
/// The amount of time to wait for further typing before querying
11+
/// completion sources via
12+
/// [`activateOnTyping`](#autocomplete.autocompletion^config.activateOnTyping).
13+
/// Defaults to 100, which should be fine unless your completion
14+
/// source is very slow and/or doesn't use `validFor`.
15+
activateOnTypingDelay?: number
1016
/// By default, when completion opens, the first option is selected
1117
/// and can be confirmed with
1218
/// [`acceptCompletion`](#autocomplete.acceptCompletion). When this
@@ -52,7 +58,7 @@ export interface CompletionConfig {
5258
/// other added widgets and the standard content. The default icons
5359
/// have position 20, the label position 50, and the detail position
5460
/// 80.
55-
addToOptions?: {render: (completion: Completion, state: EditorState) => Node | null,
61+
addToOptions?: {render: (completion: Completion, state: EditorState, view: EditorView) => Node | null,
5662
position: number}[]
5763
/// By default, [info](#autocomplete.Completion.info) tooltips are
5864
/// placed to the side of the selected completion. This option can
@@ -82,6 +88,7 @@ export const completionConfig = Facet.define<CompletionConfig, Required<Completi
8288
combine(configs) {
8389
return combineConfig<Required<CompletionConfig>>(configs, {
8490
activateOnTyping: true,
91+
activateOnTypingDelay: 100,
8592
selectOnOpen: true,
8693
override: null,
8794
closeOnBlur: true,

src/snippet.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,12 @@ export function snippet(template: string) {
197197
}
198198
}),
199199
scrollIntoView: true,
200-
annotations: completion ? pickedCompletion.of(completion) : undefined,
200+
annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined,
201201
effects: [],
202202
}
203203

204204
if (ranges.length) spec.selection = fieldSelection(ranges, 0)
205-
206-
if (ranges.length > 1) {
205+
if (ranges.some(r => r.field > 0)) {
207206
let active = new ActiveSnippet(ranges, 0)
208207
spec.effects.push(setActive.of(active))
209208
if (editor.state.field(snippetState, false) === undefined) {

src/tooltip.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {CompletionState} from "./state"
44
import {completionConfig, CompletionConfig} from "./config"
55
import {Option, Completion, CompletionInfo, closeCompletionEffect} from "./completion"
66

7-
type OptionContentSource = (completion: Completion, state: EditorState, match: readonly number[]) => Node | null
7+
type OptionContentSource =
8+
(completion: Completion, state: EditorState, view: EditorView, match: readonly number[]) => Node | null
89

910
function optionContent(config: Required<CompletionConfig>): OptionContentSource[] {
1011
let content = config.addToOptions.slice() as {render: OptionContentSource, position: number}[]
@@ -20,7 +21,7 @@ function optionContent(config: Required<CompletionConfig>): OptionContentSource[
2021
position: 20
2122
})
2223
content.push({
23-
render(completion: Completion, _s: EditorState, match: readonly number[]) {
24+
render(completion: Completion, _s: EditorState, _v: EditorView, match: readonly number[]) {
2425
let labelElt = document.createElement("span")
2526
labelElt.className = "cm-completionLabel"
2627
let label = completion.displayLabel || completion.label, off = 0
@@ -267,7 +268,7 @@ class CompletionTooltip {
267268
let cls = this.optionClass(completion)
268269
if (cls) li.className = cls
269270
for (let source of this.optionContent) {
270-
let node = source(completion, this.view.state, match)
271+
let node = source(completion, this.view.state, this.view, match)
271272
if (node) li.appendChild(node)
272273
}
273274
}

src/view.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const completionPlugin = ViewPlugin.fromClass(class implements PluginValu
7272
debounceUpdate = -1
7373
running: RunningQuery[] = []
7474
debounceAccept = -1
75+
pendingStart = false
7576
composing = CompositionState.None
7677

7778
constructor(readonly view: EditorView) {
@@ -102,8 +103,10 @@ export const completionPlugin = ViewPlugin.fromClass(class implements PluginValu
102103
}
103104

104105
if (this.debounceUpdate > -1) clearTimeout(this.debounceUpdate)
106+
if (update.transactions.some(tr => tr.effects.some(e => e.is(startCompletionEffect)))) this.pendingStart = true
107+
let delay = this.pendingStart ? 50 : update.state.facet(completionConfig).activateOnTypingDelay
105108
this.debounceUpdate = cState.active.some(a => a.state == State.Pending && !this.running.some(q => q.active.source == a.source))
106-
? setTimeout(() => this.startUpdate(), 50) : -1
109+
? setTimeout(() => this.startUpdate(), delay) : -1
107110

108111
if (this.composing != CompositionState.None) for (let tr of update.transactions) {
109112
if (getUserEvent(tr) == "input")
@@ -115,6 +118,7 @@ export const completionPlugin = ViewPlugin.fromClass(class implements PluginValu
115118

116119
startUpdate() {
117120
this.debounceUpdate = -1
121+
this.pendingStart = false
118122
let {state} = this.view, cState = state.field(completionState)
119123
for (let active of cState.active) {
120124
if (active.state == State.Pending && !this.running.some(r => r.active.source == active.source))
@@ -196,7 +200,7 @@ export const completionPlugin = ViewPlugin.fromClass(class implements PluginValu
196200
if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) {
197201
let dialog = state.open && getTooltip(this.view, state.open.tooltip)
198202
if (!dialog || !dialog.dom.contains(event.relatedTarget as HTMLElement))
199-
this.view.dispatch({effects: closeCompletionEffect.of(null)})
203+
setTimeout(() => this.view.dispatch({effects: closeCompletionEffect.of(null)}), 10)
200204
}
201205
},
202206
compositionstart() {

test/webtest-autocomplete.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ class Runner {
3737
state: EditorState.create({
3838
doc: spec.doc,
3939
selection,
40-
extensions: [autocompletion({override: spec.sources, interactionDelay: 0, updateSyncTime: 40}), EditorState.allowMultipleSelections.of(true)]
40+
extensions: [
41+
autocompletion({override: spec.sources, interactionDelay: 0, updateSyncTime: 40, activateOnTypingDelay: 10}),
42+
EditorState.allowMultipleSelections.of(true)
43+
]
4144
}),
4245
parent: document.querySelector("#workspace")! as HTMLElement,
4346
dispatchTransactions: trs => {

0 commit comments

Comments
 (0)