From d03a72e9f333f8fe0227f3872f41a667bdb65de4 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Wed, 23 Nov 2022 21:14:10 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=92=96=20Autocomplete=20automatically?= =?UTF-8?q?=20on=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/CellInput/pluto_autocomplete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js index 2fe7577eed..a2001cce34 100644 --- a/frontend/components/CellInput/pluto_autocomplete.js +++ b/frontend/components/CellInput/pluto_autocomplete.js @@ -376,7 +376,7 @@ export let pluto_autocomplete = ({ request_autocomplete, on_update_doc_query }) return [ tabCompletionState, autocompletion({ - activateOnTyping: false, + activateOnTyping: true, override: [ pluto_completion_fetcher(memoize_last_request_autocomplete), // julia_special_completions_to_cm(memoize_last_request_autocomplete), From 617238c60bdee9937f3b047423c6e5d51674ac5e Mon Sep 17 00:00:00 2001 From: Michiel Dral Date: Tue, 6 Dec 2022 15:35:56 +0000 Subject: [PATCH 2/4] Only do automatic autocomplete in cases where we know it is nice (Also fixes #2388 ?) --- .../CellInput/pluto_autocomplete.js | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js index 0c42ca12dc..5353732dda 100644 --- a/frontend/components/CellInput/pluto_autocomplete.js +++ b/frontend/components/CellInput/pluto_autocomplete.js @@ -17,7 +17,7 @@ import { get_selected_doc_from_state } from "./LiveDocsFromCursor.js" import { cl } from "../../common/ClassTable.js" import { ScopeStateField } from "./scopestate_statefield.js" -let { autocompletion, completionKeymap, completionStatus, acceptCompletion } = autocomplete +let { autocompletion, completionKeymap, completionStatus, acceptCompletion, selectedCompletion } = autocomplete // These should be imported from @codemirror/autocomplete, but they are not exported. let completionState = autocompletion()[0] @@ -47,13 +47,19 @@ const tabCompletionState = StateField.define({ for (let effect of tr.effects) { if (effect.is(TabCompletionEffect)) return true } + + // Used to use `tr.startState.field(completionState, false)?.open` instead of `selectedCompletion`, + // but as `selectedCompletion` is actual public API (and does a little more checks), I prefer that now. + let previous_selected = selectedCompletion(tr.startState) + let current_selected = selectedCompletion(tr.state) + // Autocomplete window was closed - if (tr.startState.field(completionState, false)?.open != null && tr.state.field(completionState, false)?.open == null) { + if (previous_selected != null && current_selected == null) { return false } if ( - tr.startState.field(completionState, false).open != null && - tr.startState.field(completionState, false) !== tr.state.field(completionState, false) + previous_selected != null && + previous_selected !== current_selected ) { return false } @@ -115,8 +121,11 @@ const pluto_autocomplete_keymap = [ */ let update_docs_from_autocomplete_selection = (on_update_doc_query) => { return EditorView.updateListener.of((update) => { - // Can't use this yet as it has not enough info to apply the change (source.from and source.to) - // let selected_completion = autocomplete.selectedCompletion(update.state) + // But we can use `selectedCompletion` to better check if the autocomplete is open + // (for some reason `autocompletion_state?.open != null` isn't enough anymore?) + // Sadly we still need `update.state.field(completionState, false)` as well because we can't + // apply the result from `selectedCompletion()` yet (has no .from and .to, for example) + if (selectedCompletion(update.state) == null) return let autocompletion_state = update.state.field(completionState, false) let open_autocomplete = autocompletion_state?.open @@ -129,6 +138,10 @@ let update_docs_from_autocomplete_selection = (on_update_doc_query) => { // Apply completion to state, which will yield us a `Transaction`. // The nice thing about this is that we can use the resulting state from the transaction, // without updating the actual state of the editor. + // NOTE This could bite someone who isn't familiar with this, but there isn't an easy way to fix it without a lot of console spam: + // .... THIS UPDATE WILL DO CONSOLE.LOG'S LIKE ANY UPDATE WOULD DO + // .... Which means you sometimes get double logs from codemirror extensions... + // .... Very disorienting 😵‍💫 let result_transaction = update.state.update({ changes: { from: selected_option.source.from, to: selected_option.source.to, insert: text_to_apply }, }) @@ -208,6 +221,21 @@ const generate_scopestate_completions = function* (definitions, proposed, contex const julia_code_completions_to_cm = (/** @type {PlutoRequestAutocomplete} */ request_autocomplete) => async (ctx) => { let to_complete = ctx.state.sliceDoc(0, ctx.pos) + // This block is to check if we have enough reason to show the automatic completions e.g. we are in an identifier, or after a `.`. + // Else it will show the completions always, which is just frustrating. + if (!ctx.explicit) { + let is_in_identifier = ctx.tokenBefore(["Identifier", "FieldName", "MacroIdentifier"]) != null + let is_dot_just_before = ctx.matchBefore(/\./) != null; + if (is_in_identifier || is_dot_just_before) { + // Go do the actual completion! + // I could have inverted the above condition but I find that very hard to read, so here is an `else`! + } else { + // Turns out `return null` pretends like the autocomplete didn't change... when we want to actively hide it when it was visible + // return null + return { from: ctx.pos, to: ctx.pos, options: [] } + } + } + // Another rough hack... If it detects a `.:`, we want to cut out the `:` so we get all results from julia, // but then codemirror will put the `:` back in filtering let is_symbol_completion = match_symbol_complete(ctx) From e4b21cd80bf4fe30c22ff961a93a9b5329a05be1 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Fri, 5 Apr 2024 17:57:36 +0300 Subject: [PATCH 3/4] enable by default --- frontend/components/CellInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/CellInput.js b/frontend/components/CellInput.js index ad5d2aadc1..406798cf75 100644 --- a/frontend/components/CellInput.js +++ b/frontend/components/CellInput.js @@ -63,7 +63,7 @@ import { moveLineDown } from "../imports/CodemirrorPlutoSetup.js" export const ENABLE_CM_MIXED_PARSER = window.localStorage.getItem("ENABLE_CM_MIXED_PARSER") === "true" export const ENABLE_CM_SPELLCHECK = window.localStorage.getItem("ENABLE_CM_SPELLCHECK") === "true" -export const ENABLE_CM_AUTOCOMPLETE_ON_TYPE = window.localStorage.getItem("ENABLE_CM_AUTOCOMPLETE_ON_TYPE") === "true" +export const ENABLE_CM_AUTOCOMPLETE_ON_TYPE = window.localStorage.getItem("ENABLE_CM_AUTOCOMPLETE_ON_TYPE") !== "false" if (ENABLE_CM_MIXED_PARSER) { console.log(`YOU ENABLED THE CODEMIRROR MIXED LANGUAGE PARSER From f1e4a5b93629e2af1da6c740adc9c55d06189e50 Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Thu, 11 Apr 2024 16:39:40 +0300 Subject: [PATCH 4/4] Put ctx.explicit logic in all completion sources --- .../CellInput/pluto_autocomplete.js | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/frontend/components/CellInput/pluto_autocomplete.js b/frontend/components/CellInput/pluto_autocomplete.js index 43ce5ba7e7..3e9062bdb7 100644 --- a/frontend/components/CellInput/pluto_autocomplete.js +++ b/frontend/components/CellInput/pluto_autocomplete.js @@ -190,24 +190,9 @@ const validFor = (text) => { /** Use the completion results from the Julia server to create CM completion objects. */ const julia_code_completions_to_cm = (/** @type {PlutoRequestAutocomplete} */ request_autocomplete) => async (/** @type {autocomplete.CompletionContext} */ ctx) => { - if (writing_variable_name_or_keyword(ctx)) return null if (match_special_symbol_complete(ctx)) return null - if (ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null - - // This block is to check if we have enough reason to show the automatic completions e.g. we are in an identifier, or after a `.`. - // Else it will show the completions always, which is just frustrating. - if (!ctx.explicit) { - let is_in_identifier = ctx.tokenBefore(["Identifier", "FieldName", "MacroIdentifier"]) != null - let is_dot_just_before = ctx.matchBefore(/\./) != null - if (is_in_identifier || is_dot_just_before) { - // Go do the actual completion! - // I could have inverted the above condition but I find that very hard to read, so here is an `else`! - } else { - // Turns out `return null` pretends like the autocomplete didn't change... when we want to actively hide it when it was visible - // return null - return { from: ctx.pos, to: ctx.pos, options: [] } - } - } + if (!ctx.explicit && writing_variable_name_or_keyword(ctx)) return null + if (!ctx.explicit && ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null let to_complete = /** @type {String} */ (ctx.state.sliceDoc(0, ctx.pos)) @@ -218,9 +203,6 @@ const julia_code_completions_to_cm = to_complete = to_complete.slice(0, is_symbol_completion.from + 1) + to_complete.slice(is_symbol_completion.from + 2) } - // no path autocompletions - if (ctx.tokenBefore(["String"]) != null) return null - const globals = ctx.state.facet(GlobalDefinitionsFacet) const is_already_a_global = (text) => text != null && Object.keys(globals).includes(text) @@ -306,9 +288,9 @@ const julia_code_completions_to_cm = } const complete_anyword = async (/** @type {autocomplete.CompletionContext} */ ctx) => { - if (writing_variable_name_or_keyword(ctx)) return null if (match_special_symbol_complete(ctx)) return null - if (ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null + if (!ctx.explicit && writing_variable_name_or_keyword(ctx)) return null + if (!ctx.explicit && ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null const results_from_cm = await autocomplete.completeAnyWord(ctx) if (results_from_cm === null) return null @@ -350,9 +332,9 @@ const writing_variable_name_or_keyword = (/** @type {autocomplete.CompletionCont /** @returns {Promise} */ const global_variables_completion = async (/** @type {autocomplete.CompletionContext} */ ctx) => { - if (writing_variable_name_or_keyword(ctx)) return null if (match_special_symbol_complete(ctx)) return null - if (ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null + if (!ctx.explicit && writing_variable_name_or_keyword(ctx)) return null + if (!ctx.explicit && ctx.tokenBefore(["Number", "Comment", "String", "TripleString"]) != null) return null const globals = ctx.state.facet(GlobalDefinitionsFacet) @@ -439,9 +421,9 @@ const special_symbols_completion = (/** @type {() => Promise} * } return async (/** @type {autocomplete.CompletionContext} */ ctx) => { - if (writing_variable_name_or_keyword(ctx)) return null if (!match_special_symbol_complete(ctx)) return null - if (ctx.tokenBefore(["Number", "Comment"]) != null) return null + if (!ctx.explicit && writing_variable_name_or_keyword(ctx)) return null + if (!ctx.explicit && ctx.tokenBefore(["Number", "Comment"]) != null) return null const result = await get_special_symbols()