From 46dc4bfed8b6c62ff12e5d38a0624166976608ad Mon Sep 17 00:00:00 2001 From: Michiel Dral Date: Wed, 8 Dec 2021 18:35:31 +0000 Subject: [PATCH] Full autocomplete (#2) * hmm * 0.19.4 --- README.md | 17 +++-- dist/index.d.ts | 172 ++++++++++++++++++++++++++++++++++++++++++++- dist/index.es.js | 143 ++++++++++++++++++++++++++++++++++++- package-lock.json | 12 ++-- package.json | 4 +- src/basic-setup.ts | 4 +- 6 files changed, 329 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index dc2ca80..31539e9 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,35 @@ - # codemirror-pluto-setup - [Pluto](https://github.com/fonsp/Pluto.jl) uses the awesome codemirror 6, but Pluto is 'buildelss', which means that we need to import dependencies directly from the jsdelivr CDN. Codemirror 6 is not meant to work in this setup. Using tools such as https://esm.sh/ , https://www.jsdelivr.com/esm or https://www.skypack.dev/ does not work in our case, because: + - downstream dependencies cannot be version-pinned easily. For pluto we want to version-pin everything. - the modular design of codemirror 6 requires multiple modules to use the same version/instance of shared dependencies, such as `@codemirror/state`. This is hard/impossible with ES building CDNs. This is why we have this repository to manage our dependencies and to export a single `.es.js` and `.d.ts` file. - # How to update 1. `npm install` -1. Change the `index.ts` file. +1. Change the `basic-setup.ts` file. 1. `npm run bundle` - # How to release and update pluto + 1. `npm run bundle` -2. Change the `package.json` patch version number +1 +2. Change the `package.json` patch version number +1 (`npm version patch`) 3. git commit etc 4. Go here: https://github.com/JuliaPluto/codemirror-pluto-setup/releases/new 5. For tag name, type in the new version number (without `v`) and select "create new tag on publish" 6. Type version name (without `v`) as release title 7. Release 8. In Pluto: update `frontend/imports/CodemirrorPlutoSetup.js` with the new version number. -8. Copy the contents of `dist/index.d.ts` and In Pluto: paste into `frontend/imports/CodemirrorPlutoSetup.d.ts`. Save without formatting. -9. git commit etc +9. Copy the contents of `dist/index.d.ts` and In Pluto: paste into `frontend/imports/CodemirrorPlutoSetup.d.ts`. Save without formatting. +10. git commit etc ## To test something inside pluto without publishing a version: + 1. `npm run bundle` and commit to a new branch. Get the commit hash, e.g. `bab65fc` -2. In Pluto, use the commit hash as your version number, e.g. +2. In Pluto, use the commit hash as your version number, e.g. ```js import { ... } from "https://cdn.jsdelivr.net/gh/JuliaPluto/codemirror-pluto-setup@bab65fc/dist/index.es.min.js" diff --git a/dist/index.d.ts b/dist/index.d.ts index 0deccc3..60e3671 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -4491,6 +4491,21 @@ declare class CompletionContext { addEventListener(type: "abort", listener: () => void): void; } /** +Given a a fixed array of options, return an autocompleter that +completes them. +*/ +declare function completeFromList(list: readonly (string | Completion)[]): CompletionSource; +/** +Wrap the given completion source so that it will only fire when the +cursor is in a syntax node with one of the given names. +*/ +declare function ifIn(nodes: readonly string[], source: CompletionSource): CompletionSource; +/** +Wrap the given completion source so that it will not fire when the +cursor is in a syntax node with one of the given names. +*/ +declare function ifNotIn(nodes: readonly string[], source: CompletionSource): CompletionSource; +/** The function signature for a completion source. Such a function may return its [result](https://codemirror.net/6/docs/ref/#autocomplete.CompletionResult) synchronously or as a promise. Returning null indicates no @@ -4536,6 +4551,92 @@ interface CompletionResult { */ filter?: boolean; } +/** +This annotation is added to transactions that are produced by +picking a completion. +*/ +declare const pickedCompletion: AnnotationType; + +/** +Convert a snippet template to a function that can apply it. +Snippets are written using syntax like this: + + "for (let ${index} = 0; ${index} < ${end}; ${index}++) {\n\t${}\n}" + +Each `${}` placeholder (you may also use `#{}`) indicates a field +that the user can fill in. Its name, if any, will be the default +content for the field. + +When the snippet is activated by calling the returned function, +the code is inserted at the given position. Newlines in the +template are indented by the indentation of the start line, plus +one [indent unit](https://codemirror.net/6/docs/ref/#language.indentUnit) per tab character after +the newline. + +On activation, (all instances of) the first field are selected. +The user can move between fields with Tab and Shift-Tab as long as +the fields are active. Moving to the last field or moving the +cursor out of the current field deactivates the fields. + +The order of fields defaults to textual order, but you can add +numbers to placeholders (`${1}` or `${1:defaultText}`) to provide +a custom order. +*/ +declare function snippet(template: string): (editor: { + state: EditorState; + dispatch: (tr: Transaction) => void; +}, _completion: Completion, from: number, to: number) => void; +/** +A command that clears the active snippet, if any. +*/ +declare const clearSnippet: StateCommand; +/** +Move to the next snippet field, if available. +*/ +declare const nextSnippetField: StateCommand; +/** +Move to the previous snippet field, if available. +*/ +declare const prevSnippetField: StateCommand; +/** +A facet that can be used to configure the key bindings used by +snippets. The default binds Tab to +[`nextSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.nextSnippetField), Shift-Tab to +[`prevSnippetField`](https://codemirror.net/6/docs/ref/#autocomplete.prevSnippetField), and Escape +to [`clearSnippet`](https://codemirror.net/6/docs/ref/#autocomplete.clearSnippet). +*/ +declare const snippetKeymap: Facet; +/** +Create a completion from a snippet. Returns an object with the +properties from `completion`, plus an `apply` function that +applies the snippet. +*/ +declare function snippetCompletion(template: string, completion: Completion): Completion; + +/** +Returns a command that moves the completion selection forward or +backward by the given amount. +*/ +declare function moveCompletionSelection(forward: boolean, by?: "option" | "page"): Command; +/** +Accept the current completion. +*/ +declare const acceptCompletion: Command; +/** +Explicitly start autocompletion. +*/ +declare const startCompletion: Command; +/** +Close the currently active completion. +*/ +declare const closeCompletion: Command; + +/** +A completion source that will scan the document for words (using a +[character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and +return those as completions. +*/ +declare const completeAnyWord: CompletionSource; /** Returns an extension that enables autocompletion. @@ -4553,6 +4654,75 @@ Basic keybindings for autocompletion. - Enter: [`acceptCompletion`](https://codemirror.net/6/docs/ref/#autocomplete.acceptCompletion) */ declare const completionKeymap: readonly KeyBinding[]; +/** +Get the current completion status. When completions are available, +this will return `"active"`. When completions are pending (in the +process of being queried), this returns `"pending"`. Otherwise, it +returns `null`. +*/ +declare function completionStatus(state: EditorState): null | "active" | "pending"; +/** +Returns the available completions as an array. +*/ +declare function currentCompletions(state: EditorState): readonly Completion[]; +/** +Return the currently selected completion, if any. +*/ +declare function selectedCompletion(state: EditorState): Completion | null; + +type index_Completion = Completion; +type index_CompletionContext = CompletionContext; +declare const index_CompletionContext: typeof CompletionContext; +type index_CompletionResult = CompletionResult; +type index_CompletionSource = CompletionSource; +declare const index_acceptCompletion: typeof acceptCompletion; +declare const index_autocompletion: typeof autocompletion; +declare const index_clearSnippet: typeof clearSnippet; +declare const index_closeCompletion: typeof closeCompletion; +declare const index_completeAnyWord: typeof completeAnyWord; +declare const index_completeFromList: typeof completeFromList; +declare const index_completionKeymap: typeof completionKeymap; +declare const index_completionStatus: typeof completionStatus; +declare const index_currentCompletions: typeof currentCompletions; +declare const index_ifIn: typeof ifIn; +declare const index_ifNotIn: typeof ifNotIn; +declare const index_moveCompletionSelection: typeof moveCompletionSelection; +declare const index_nextSnippetField: typeof nextSnippetField; +declare const index_pickedCompletion: typeof pickedCompletion; +declare const index_prevSnippetField: typeof prevSnippetField; +declare const index_selectedCompletion: typeof selectedCompletion; +declare const index_snippet: typeof snippet; +declare const index_snippetCompletion: typeof snippetCompletion; +declare const index_snippetKeymap: typeof snippetKeymap; +declare const index_startCompletion: typeof startCompletion; +declare namespace index { + export { + index_Completion as Completion, + index_CompletionContext as CompletionContext, + index_CompletionResult as CompletionResult, + index_CompletionSource as CompletionSource, + index_acceptCompletion as acceptCompletion, + index_autocompletion as autocompletion, + index_clearSnippet as clearSnippet, + index_closeCompletion as closeCompletion, + index_completeAnyWord as completeAnyWord, + index_completeFromList as completeFromList, + index_completionKeymap as completionKeymap, + index_completionStatus as completionStatus, + index_currentCompletions as currentCompletions, + index_ifIn as ifIn, + index_ifNotIn as ifNotIn, + index_moveCompletionSelection as moveCompletionSelection, + index_nextSnippetField as nextSnippetField, + index_pickedCompletion as pickedCompletion, + index_prevSnippetField as prevSnippetField, + index_selectedCompletion as selectedCompletion, + index_snippet as snippet, + index_snippetCompletion as snippetCompletion, + index_snippetKeymap as snippetKeymap, + index_startCompletion as startCompletion, + }; +} declare type HighlightOptions = { /** @@ -4939,4 +5109,4 @@ Create an instance of the collaborative editing plugin. */ declare function collab(config?: CollabConfig): Extension; -export { Annotation, Compartment, Decoration, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, StreamLanguage, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, autocompletion, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, commentKeymap, completionKeymap, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, julia$1 as julia_legacy, keymap, lineNumbers, markdown, markdownLanguage, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, sql, syntaxTree, tags }; +export { Annotation, Compartment, Decoration, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, StreamLanguage, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, index as autocomplete, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, commentKeymap, completionKeymap, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, julia$1 as julia_legacy, keymap, lineNumbers, markdown, markdownLanguage, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, sql, syntaxTree, tags }; diff --git a/dist/index.es.js b/dist/index.es.js index da42950..2dd071a 100644 --- a/dist/index.es.js +++ b/dist/index.es.js @@ -18307,6 +18307,18 @@ function completeFromList(list) { }; } /** +Wrap the given completion source so that it will only fire when the +cursor is in a syntax node with one of the given names. +*/ +function ifIn(nodes, source) { + return (context) => { + for (let pos = syntaxTree(context.state).resolveInner(context.pos, -1); pos; pos = pos.parent) + if (nodes.indexOf(pos.name) > -1) + return source(context); + return null; + }; +} +/** Wrap the given completion source so that it will not fire when the cursor is in a syntax node with one of the given names. */ @@ -19482,6 +19494,83 @@ const snippetPointerHandler = /*@__PURE__*/EditorView.domEventHandlers({ } }); +function wordRE(wordChars) { + let escaped = wordChars.replace(/[\\[.+*?(){|^$]/g, "\\$&"); + try { + return new RegExp(`[\\p{Alphabetic}\\p{Number}_${escaped}]+`, "ug"); + } + catch (_a) { + return new RegExp(`[\w${escaped}]`, "g"); + } +} +function mapRE(re, f) { + return new RegExp(f(re.source), re.unicode ? "u" : ""); +} +const wordCaches = /*@__PURE__*/Object.create(null); +function wordCache(wordChars) { + return wordCaches[wordChars] || (wordCaches[wordChars] = new WeakMap); +} +function storeWords(doc, wordRE, result, seen, ignoreAt) { + for (let lines = doc.iterLines(), pos = 0; !lines.next().done;) { + let { value } = lines, m; + wordRE.lastIndex = 0; + while (m = wordRE.exec(value)) { + if (!seen[m[0]] && pos + m.index != ignoreAt) { + result.push({ type: "text", label: m[0] }); + seen[m[0]] = true; + if (result.length >= 2000 /* MaxList */) + return; + } + } + pos += value.length + 1; + } +} +function collectWords(doc, cache, wordRE, to, ignoreAt) { + let big = doc.length >= 1000 /* MinCacheLen */; + let cached = big && cache.get(doc); + if (cached) + return cached; + let result = [], seen = Object.create(null); + if (doc.children) { + let pos = 0; + for (let ch of doc.children) { + if (ch.length >= 1000 /* MinCacheLen */) { + for (let c of collectWords(ch, cache, wordRE, to - pos, ignoreAt - pos)) { + if (!seen[c.label]) { + seen[c.label] = true; + result.push(c); + } + } + } + else { + storeWords(ch, wordRE, result, seen, ignoreAt - pos); + } + pos += ch.length + 1; + } + } + else { + storeWords(doc, wordRE, result, seen, ignoreAt); + } + if (big && result.length < 2000 /* MaxList */) + cache.set(doc, result); + return result; +} +/** +A completion source that will scan the document for words (using a +[character categorizer](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer)), and +return those as completions. +*/ +const completeAnyWord = context => { + let wordChars = context.state.languageDataAt("wordChars", context.pos).join(""); + let re = wordRE(wordChars); + let token = context.matchBefore(mapRE(re, s => s + "$")); + if (!token && !context.explicit) + return null; + let from = token ? token.from : context.pos; + let options = collectWords(context.state.doc, wordCache(wordChars), re, 50000 /* Range */, from); + return { from, options, span: mapRE(re, s => "^" + s) }; +}; + /** Returns an extension that enables autocompletion. */ @@ -19515,6 +19604,58 @@ const completionKeymap = [ { key: "Enter", run: acceptCompletion } ]; const completionKeymapExt = /*@__PURE__*/Prec.highest(/*@__PURE__*/keymap.computeN([completionConfig], state => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : [])); +/** +Get the current completion status. When completions are available, +this will return `"active"`. When completions are pending (in the +process of being queried), this returns `"pending"`. Otherwise, it +returns `null`. +*/ +function completionStatus(state) { + let cState = state.field(completionState, false); + return cState && cState.active.some(a => a.state == 1 /* Pending */) ? "pending" + : cState && cState.active.some(a => a.state != 0 /* Inactive */) ? "active" : null; +} +/** +Returns the available completions as an array. +*/ +function currentCompletions(state) { + var _a; + let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open; + return open ? open.options.map(o => o.completion) : []; +} +/** +Return the currently selected completion, if any. +*/ +function selectedCompletion(state) { + var _a; + let open = (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.open; + return open ? open.options[open.selected].completion : null; +} + +var index = /*#__PURE__*/Object.freeze({ + __proto__: null, + CompletionContext: CompletionContext, + acceptCompletion: acceptCompletion, + autocompletion: autocompletion, + clearSnippet: clearSnippet, + closeCompletion: closeCompletion, + completeAnyWord: completeAnyWord, + completeFromList: completeFromList, + completionKeymap: completionKeymap, + completionStatus: completionStatus, + currentCompletions: currentCompletions, + ifIn: ifIn, + ifNotIn: ifNotIn, + moveCompletionSelection: moveCompletionSelection, + nextSnippetField: nextSnippetField, + pickedCompletion: pickedCompletion, + prevSnippetField: prevSnippetField, + selectedCompletion: selectedCompletion, + snippet: snippet, + snippetCompletion: snippetCompletion, + snippetKeymap: snippetKeymap, + startCompletion: startCompletion +}); function delimitedStrategy(context, units, closing) { let after = context.textAfter; @@ -27770,4 +27911,4 @@ function collab(config = {}) { return [collabField, collabConfig.of(Object.assign({ generatedID: Math.floor(Math.random() * 1e9).toString(36) }, config))]; } -export { Annotation, Compartment, Decoration, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, StreamLanguage, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, autocompletion, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, commentKeymap, completionKeymap, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, julia$1 as julia_legacy, keymap, lineNumbers, markdown, markdownLanguage, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, sql, syntaxTree, tags$1 as tags }; +export { Annotation, Compartment, Decoration, EditorSelection, EditorState, EditorView, Facet, HighlightStyle, NodeProp, PostgreSQL, SelectionRange, StateEffect, StateField, StreamLanguage, Text, Transaction, TreeCursor, ViewPlugin, ViewUpdate, WidgetType, index as autocomplete, bracketMatching, closeBrackets, closeBracketsKeymap, collab, combineConfig, commentKeymap, completionKeymap, defaultHighlightStyle, defaultKeymap, drawSelection, foldGutter, foldKeymap, highlightSelectionMatches, highlightSpecialChars, history, historyKeymap, html, htmlLanguage, indentLess, indentMore, indentOnInput, indentUnit, javascript, javascriptLanguage, julia as julia_andrey, julia$1 as julia_legacy, keymap, lineNumbers, markdown, markdownLanguage, parseMixed, placeholder, python, pythonLanguage, rectangularSelection, searchKeymap, sql, syntaxTree, tags$1 as tags }; diff --git a/package-lock.json b/package-lock.json index 66580af..01b99e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "codemirror-pluto-setup", - "version": "0.19.3", + "version": "0.19.4", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "0.19.3", + "name": "codemirror-pluto-setup", + "version": "0.19.4", "license": "MIT", "dependencies": { - "@codemirror/autocomplete": "^0.19.3", + "@codemirror/autocomplete": "^0.19.9", "@codemirror/basic-setup": "^0.19.0", "@codemirror/closebrackets": "^0.19.0", "@codemirror/collab": "^0.19.0", @@ -852,7 +853,6 @@ "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -1785,9 +1785,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.60.2.tgz", "integrity": "sha512-1Bgjpq61sPjgoZzuiDSGvbI1tD91giZABgjCQBKM5aYLnzjq52GoDuWVwT/cm/MCxCMPU8gqQvkj8doQ5C8Oqw==", "dev": true, - "dependencies": { - "fsevents": "~2.3.2" - }, "bin": { "rollup": "dist/bin/rollup" }, @@ -1804,7 +1801,6 @@ "integrity": "sha512-hswlsdWu/x7k5pXzaLP6OvKRKcx8Bzprksz9i9mUe72zvt8LvqAb/AZpzs6FkLgmyRaN8B6rUQOVtzA3yEt9Yw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.12.13", "magic-string": "^0.25.7" }, "engines": { diff --git a/package.json b/package.json index ff47846..27d5d4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codemirror-pluto-setup", - "version": "0.19.3", + "version": "0.19.4", "description": "Example configuration for the CodeMirror code editor - as used by Pluto.jl", "scripts": { "bundle": "rollup -c" @@ -31,7 +31,7 @@ "sideEffects": false, "license": "MIT", "dependencies": { - "@codemirror/autocomplete": "^0.19.3", + "@codemirror/autocomplete": "^0.19.9", "@codemirror/basic-setup": "^0.19.0", "@codemirror/closebrackets": "^0.19.0", "@codemirror/collab": "^0.19.0", diff --git a/src/basic-setup.ts b/src/basic-setup.ts index 858b4db..73e5de1 100644 --- a/src/basic-setup.ts +++ b/src/basic-setup.ts @@ -23,7 +23,7 @@ import { rectangularSelection } from "@codemirror/rectangular-selection" import { foldGutter, foldKeymap } from "@codemirror/fold" import { bracketMatching } from "@codemirror/matchbrackets" import { closeBrackets, closeBracketsKeymap } from "@codemirror/closebrackets" -import { autocompletion } from "@codemirror/autocomplete" +import * as autocomplete from "@codemirror/autocomplete"; import { highlightSelectionMatches, searchKeymap } from "@codemirror/search" import { completionKeymap } from "@codemirror/autocomplete" import { commentKeymap } from "@codemirror/comment" @@ -59,7 +59,7 @@ export { tags, HighlightStyle, syntaxTree, - autocompletion, + autocomplete, lineNumbers, highlightSpecialChars, foldGutter,