Skip to content

Commit

Permalink
autocomplete: handle definitions from other cells (#3056)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp authored Oct 14, 2024
1 parent 3674035 commit 56256df
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 42 deletions.
14 changes: 14 additions & 0 deletions frontend/components/CellInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,17 @@ export const CellInput = ({
}
})

const unsubmitted_globals_updater = EditorView.updateListener.of((update) => {
if (update.docChanged) {
const before = [...update.startState.field(ScopeStateField).definitions.keys()]
const after = [...update.state.field(ScopeStateField).definitions.keys()]

if (!_.isEqual(before, after)) {
pluto_actions.set_unsubmitted_global_definitions(cell_id, after)
}
}
})

const usesDarkTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
const newcm = (newcm_ref.current = new EditorView({
state: EditorState.create({
Expand Down Expand Up @@ -597,6 +608,7 @@ export const CellInput = ({
highlightSelectionMatches({ minSelectionLength: 2, wholeWords: true }),
bracketMatching(),
docs_updater,
unsubmitted_globals_updater,
tab_help_plugin,
// Remove selection on blur
EditorView.domEventHandlers({
Expand Down Expand Up @@ -668,6 +680,8 @@ export const CellInput = ({
},
request_special_symbols: () => pluto_actions.send("complete_symbols").then(({ message }) => message),
on_update_doc_query: on_update_doc_query,
request_unsubmitted_global_definitions: () => pluto_actions.get_unsubmitted_global_definitions(),
cell_id,
}),

// I put plutoKeyMaps separately because I want make sure we have
Expand Down
1 change: 1 addition & 0 deletions frontend/components/CellInput/go_to_definition_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ let get_variable_marks = (state, { scopestate, global_definitions }) => {
const filter_non_null = (xs) => /** @type {Array<T>} */ (xs.filter((x) => x != null))

/**
* Key: variable name, value: cell id.
* @type {Facet<{ [variable_name: string]: string }, { [variable_name: string]: string }>}
*/
export const GlobalDefinitionsFacet = Facet.define({
Expand Down
80 changes: 48 additions & 32 deletions frontend/components/CellInput/pluto_autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,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) => {
(/** @type {PlutoRequestAutocomplete} */ request_autocomplete) =>
/** @returns {Promise<autocomplete.CompletionResult?>} */
async (/** @type {autocomplete.CompletionContext} */ ctx) => {
if (match_special_symbol_complete(ctx)) 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
Expand All @@ -185,7 +187,6 @@ const julia_code_completions_to_cm =
}

const globals = ctx.state.facet(GlobalDefinitionsFacet)
console.log(globals)
const is_already_a_global = (text) => text != null && Object.keys(globals).includes(text)

let found = await request_autocomplete({ text: to_complete })
Expand Down Expand Up @@ -324,36 +325,49 @@ const writing_variable_name_or_keyword = (/** @type {autocomplete.CompletionCont
return just_finished_a_keyword || after_keyword || inside_do_argument_expression || inside_assigment_lhs
}

/** @returns {Promise<autocomplete.CompletionResult?>} */
const global_variables_completion = async (/** @type {autocomplete.CompletionContext} */ ctx) => {
if (match_special_symbol_complete(ctx)) 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)
const global_variables_completion =
(/** @type {() => { [uuid: String]: String[]}} */ request_unsubmitted_global_definitions, cell_id) =>
/** @returns {Promise<autocomplete.CompletionResult?>} */
async (/** @type {autocomplete.CompletionContext} */ ctx) => {
if (match_special_symbol_complete(ctx)) 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

// see `is_wc_cat_id_start` in Julia's source for a complete list
const there_is_a_dot_before = ctx.matchBefore(/\.[\p{L}\p{Nl}\p{Sc}\d_!]*$/u)
if (there_is_a_dot_before) return null
// see `is_wc_cat_id_start` in Julia's source for a complete list
const there_is_a_dot_before = ctx.matchBefore(/\.[\p{L}\p{Nl}\p{Sc}\d_!]*$/u)
if (there_is_a_dot_before) return null

const from_cm = await autocomplete.completeFromList(
Object.keys(globals).map((label) => {
return {
label,
apply: label,
type: from_notebook_type,
section: section_regular,
}
})
)(ctx)
return from_cm == null
? null
: {
...from_cm,
validFor,
commitCharacters: julia_commit_characters(ctx),
}
}
const globals = ctx.state.facet(GlobalDefinitionsFacet)
const local_globals = request_unsubmitted_global_definitions()

const possibles = _.union(
// Globals that are not redefined locally
Object.entries(globals)
.filter(([_, cell_id]) => local_globals[cell_id] == null)
.map(([name]) => name),
// Globals that are redefined locally in other cells
...Object.values(_.omit(local_globals, cell_id))
)

const from_cm = await autocomplete.completeFromList(
possibles.map((label) => {
return {
label,
apply: label,
type: from_notebook_type,
section: section_regular,
// boost: 1,
}
})
)(ctx)
return from_cm == null
? null
: {
...from_cm,
validFor,
commitCharacters: julia_commit_characters(ctx),
}
}

const local_variables_completion = (/** @type {autocomplete.CompletionContext} */ ctx) => {
let scopestate = ctx.state.field(ScopeStateField)
Expand Down Expand Up @@ -497,8 +511,10 @@ const continue_completing_path = EditorView.updateListener.of((update) => {
* @param {PlutoRequestAutocomplete} props.request_autocomplete
* @param {() => Promise<SpecialSymbols?>} props.request_special_symbols
* @param {(query: string) => void} props.on_update_doc_query
* @param {() => { [uuid: string] : String[]}} props.request_unsubmitted_global_definitions
* @param {string} props.cell_id
*/
export let pluto_autocomplete = ({ request_autocomplete, request_special_symbols, on_update_doc_query }) => {
export let pluto_autocomplete = ({ request_autocomplete, request_special_symbols, on_update_doc_query, request_unsubmitted_global_definitions, cell_id }) => {
let last_query = null
let last_result = null
/**
Expand All @@ -523,7 +539,7 @@ export let pluto_autocomplete = ({ request_autocomplete, request_special_symbols
autocompletion({
activateOnTyping: ENABLE_CM_AUTOCOMPLETE_ON_TYPE,
override: [
global_variables_completion,
global_variables_completion(request_unsubmitted_global_definitions, cell_id),
special_symbols_completion(request_special_symbols),
julia_code_completions_to_cm(memoize_last_request_autocomplete),
complete_anyword,
Expand Down
36 changes: 26 additions & 10 deletions frontend/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export const url_logo_small = document.head.querySelector("link[rel='pluto-logo-
* @type {{
* notebook: NotebookData,
* cell_inputs_local: { [uuid: string]: { code: String } },
* unsumbitted_global_definitions: { [uuid: string]: String[] }
* desired_doc_query: ?String,
* recently_deleted: ?Array<{ index: number, cell: CellInputData }>,
* recently_auto_disabled_cells: Record<string,[string,string]>,
Expand Down Expand Up @@ -315,6 +316,7 @@ export class Editor extends Component {
this.state = {
notebook: initial_notebook_state,
cell_inputs_local: {},
unsumbitted_global_definitions: {},
desired_doc_query: null,
recently_deleted: [],
recently_auto_disabled_cells: {},
Expand Down Expand Up @@ -376,6 +378,14 @@ export class Editor extends Component {
})
)
},
set_unsubmitted_global_definitions: (cell_id, new_val) => {
return this.setStatePromise(
immer((/** @type {EditorState} */ state) => {
state.unsumbitted_global_definitions[cell_id] = new_val
})
)
},
get_unsubmitted_global_definitions: () => _.pick(this.state.unsumbitted_global_definitions, this.state.notebook.cell_order),
focus_on_neighbor: (cell_id, delta, line = delta === -1 ? Infinity : -1, ch = 0) => {
const i = this.state.notebook.cell_order.indexOf(cell_id)
const new_i = i + delta
Expand Down Expand Up @@ -564,15 +574,20 @@ export class Editor extends Component {
this.actions.interrupt_remote(cell_ids[0])
}
} else {
this.setState({
recently_deleted: cell_ids.map((cell_id) => {
return {
index: this.state.notebook.cell_order.indexOf(cell_id),
cell: this.state.notebook.cell_inputs[cell_id],
this.setState(
immer((/** @type {EditorState} */ state) => {
state.recently_deleted = cell_ids.map((cell_id) => {
return {
index: this.state.notebook.cell_order.indexOf(cell_id),
cell: this.state.notebook.cell_inputs[cell_id],
}
})
state.selected_cells = []
for (let c of cell_ids) {
delete state.unsumbitted_global_definitions[c]
}
}),
selected_cells: [],
})
})
)
await update_notebook((notebook) => {
for (let cell_id of cell_ids) {
delete notebook.cell_inputs[cell_id]
Expand Down Expand Up @@ -617,11 +632,12 @@ export class Editor extends Component {
}
}
})
// This is a "dirty" trick, as this should actually be stored in some shared request_status => status state
// But for now... this is fine 😼
await this.setStatePromise(
immer((/** @type {EditorState} */ state) => {
for (let cell_id of cell_ids) {
delete state.unsumbitted_global_definitions[cell_id]
// This is a "dirty" trick, as this should actually be stored in some shared request_status => status state
// But for now... this is fine 😼
if (state.notebook.cell_results[cell_id] != null) {
state.notebook.cell_results[cell_id].queued = this.is_process_ready()
} else {
Expand Down

0 comments on commit 56256df

Please sign in to comment.