diff --git a/ivette/src/sandbox/codemirror6.tsx b/ivette/src/sandbox/codemirror6.tsx index b7fa41bec15f3f6d19e2e4e508539efd2985edae..b2336e5450a1ef0d9d8bc5d8c07a7f107becfdf2 100644 --- a/ivette/src/sandbox/codemirror6.tsx +++ b/ivette/src/sandbox/codemirror6.tsx @@ -1,18 +1,18 @@ import React from 'react'; -import Dictionary from 'lodash'; +import Lodash from 'lodash'; import { EditorState, StateField, Facet, Extension } from '@codemirror/state'; import { Annotation, Transaction, RangeSet } from '@codemirror/state'; -import { Decoration, DecorationSet } from '@codemirror/view'; import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view'; -import { DOMEventHandlers, GutterMarker, gutter } from '@codemirror/view'; - -import { tags } from '@lezer/highlight'; -import { syntaxHighlighting, HighlightStyle } from '@codemirror/language'; +import { Decoration, DecorationSet } from '@codemirror/view'; +import { DOMEventMap as EventMap } from '@codemirror/view'; +import { GutterMarker, gutter } from '@codemirror/view'; import { parser } from '@lezer/cpp'; +import { tags } from '@lezer/highlight'; import { SyntaxNode } from '@lezer/common'; import { foldGutter, foldNodeProp } from '@codemirror/language'; import { LRLanguage, LanguageSupport } from "@codemirror/language"; +import { syntaxHighlighting, HighlightStyle } from '@codemirror/language'; import * as Dome from 'dome'; import * as Utils from 'dome/data/arrays'; @@ -32,19 +32,18 @@ import './dark-code.css'; // ----------------------------------------------------------------------------- -// Helper types definitions +// CodeMirror state's extensions types definitions // ----------------------------------------------------------------------------- +// Helper types definitions export type Get<A> = (state: EditorState | undefined) => A; export type Set<A> = (view: EditorView | null, value: A) => void; export type Equal<A> = (left: A, right: A) => boolean; export interface Data<A, S> { init: A, get: Get<A>, structure: S } - - -// ----------------------------------------------------------------------------- -// CodeMirror's Fields -// ----------------------------------------------------------------------------- +// Event handlers type definition. +export type Handler<I, E> = (i: I, v: EditorView, e: E) => void; +export type Handlers<I> = { [e in keyof EventMap]?: Handler<I, EventMap[e]> }; // A Field is a data added to the CodeMirror internal state that can be // modified by the outside world and used by plugins. The typical use case is @@ -57,29 +56,6 @@ export interface Data<A, S> { init: A, get: Get<A>, structure: S } // is that it is needed during the Aspects creation's process. export interface Field<A> extends Data<A, StateField<A>> { set: Set<A> } -// A Field is simply declared using an initial value. However, to be able to -// use it, you must add its extension (obtained through <field.extension>) to -// the CodeMirror initial configuration. If determining equality between -// values of the given type cannot be done using (===), an equality test can be -// provided through the optional parameters <equal>. -export function createField<A>(init: A, equal?: Equal<A>): Field<A> { - const annot = Annotation.define<A>(); - const create = (): A => init; - type Update<A> = (current: A, transaction: Transaction) => A; - const update: Update<A> = (current, tr) => tr.annotation(annot) ?? current; - const field = StateField.define<A>({ create, update, compare: equal }); - const get: Get<A> = (state) => state?.field(field) ?? init; - const useSet: Set<A> = (v, a) => - React.useEffect(() => v?.dispatch({ annotations: annot.of(a) }), [v, a]); - return { init, get, set: useSet, structure: field }; -} - - - -// ----------------------------------------------------------------------------- -// CodeMirror's Aspects -// ----------------------------------------------------------------------------- - // An Aspect is a data associated with an editor state and computed by combining // data from several fields. A typical use case is if one needs a data that // relies on a server side information (like a synchronized array) which must be @@ -89,9 +65,22 @@ export function createField<A>(init: A, equal?: Equal<A>): Field<A> { // the CodeMirror initial configuration. export interface Aspect<A> extends Data<A, Facet<A, A>> { extension: Extension } -// An Aspect is recomputed each time its dependencies are updated. The -// dependencies of an Aspect is declared through a record, giving a name to each -// dependency. + + +// ----------------------------------------------------------------------------- +// CodeMirror state's extensions dependencies +// ----------------------------------------------------------------------------- + +// State extensions and Aspects have to declare their dependencies, i.e. the +// Field and Aspects they rely on to perform their computations. Dependencies +// are declared as a record mapping names to either a Field or an Aspect. This +// is needed to be able to give the dependencies values to the computing +// functions in a typed manner. However, an important point to take into +// consideration is that the extensions constructors defined below cannot +// actually typecheck without relying on type assertions. It means that if you +// declare an extension's dependencies after creating the extension, it will +// crash at execution time. So please, check that every dependency is declared +// before being used. export type Dict = Record<string, unknown>; export type Dependency<A> = Field<A> | Aspect<A>; export type Dependencies<I extends Dict> = { [K in keyof I]: Dependency<I[K]> }; @@ -113,6 +102,14 @@ function transformDict<I extends Dict>(deps: Deps<I>, tr: Transform<I>): Dict { return Object.fromEntries(Object.keys(deps).map(k => [k, tr(deps[k], k)])); } +// Helper function retrieving the current values associated to each dependencies +// in a given editor state. They are returned as a Dict instead of the precise +// type because of TypeScript subtyping shenanigans that prevent us to correctly +// type the returned record. Thus, a type assertion has to be used. +function inputs<I extends Dict>(ds: Deps<I>, s: EditorState | undefined): Dict { + return transformDict(ds, (d) => d.get(s)); +} + // Helper function retrieving a dependency extension. function getExtension<A>(dep: Dependency<A>): Extension { type Dep<A> = Dependency<A>; @@ -122,6 +119,42 @@ function getExtension<A>(dep: Dependency<A>): Extension { else return dep.structure.extension; } + + +// ----------------------------------------------------------------------------- +// CodeMirror state's extensions +// ----------------------------------------------------------------------------- + +// Several extensions constructors are provided. Each one of them encapsulates +// its dependencies if needed. This means that adding an extension to the +// CodeMirror's initial configuration will also add all its dependencies' +// extensions, and thus recursively. However, for now, there is no systemic way +// to check that the fields at the root of the dependencies tree are updated by +// a component. This means you have to verify, by hands, that every field is +// updated when needed by your component. It may be possible to compute a +// function that asks for a value for each fields in the dependencies tree and +// does the update for you, thus forcing you to actually update every fields. +// But it seems hard to define and harder to type. + +// A Field is simply declared using an initial value. However, to be able to +// use it, you must add its extension (obtained through <field.extension>) to +// the CodeMirror initial configuration. If determining equality between +// values of the given type cannot be done using (===), an equality test can be +// provided through the optional parameters <equal>. Providing an equality test +// for complex types can help improve performances by avoiding recomputing +// extensions depending on the field. +export function createField<A>(init: A, equal?: Equal<A>): Field<A> { + const annot = Annotation.define<A>(); + const create = (): A => init; + type Update<A> = (current: A, transaction: Transaction) => A; + const update: Update<A> = (current, tr) => tr.annotation(annot) ?? current; + const field = StateField.define<A>({ create, update, compare: equal }); + const get: Get<A> = (state) => state?.field(field) ?? init; + const useSet: Set<A> = (v, a) => + React.useEffect(() => v?.dispatch({ annotations: annot.of(a) }), [v, a]); + return { init, get, set: useSet, structure: field }; +} + // An Aspect is declared using its dependencies and a function. This function's // input is a record containing, for each key of the dependencies record, a // value of the type of the corresponding field. The function's output is a @@ -130,80 +163,61 @@ export function createAspect<I extends Dict, O>( deps: Dependencies<I>, fn: (input: I) => O, ): Aspect<O> { - const init = fn(transformDict(deps, (d) => d.init) as I); const enables = mapDict(deps, getExtension); + const init = fn(transformDict(deps, (d) => d.init) as I); const combine: Combine<O> = (l) => l.length > 0 ? l[l.length - 1] : init; const facet = Facet.define<O, O>({ combine, enables }); const get: Get<O> = (state) => state?.facet(facet) ?? init; const convertedDeps = mapDict(deps, (d) => d.structure); - const compute: Get<O> = (s) => fn(transformDict(deps, (d) => d.get(s)) as I); + const compute: Get<O> = (s) => fn(inputs(deps, s) as I); const extension = facet.compute(convertedDeps, compute); return { init, get, structure: facet, extension }; } +// A Decorator is an extension that adds decorations to the CodeMirror's +// document, i.e. it tags subpart of the document with CSS classes. See the +// CodeMirror's documentation on Decoration for further details. +export function createDecorator<I extends Dict>( + deps: Dependencies<I>, + fn: (inputs: I, state: EditorState) => DecorationSet +): Extension { + const enables = mapDict(deps, getExtension); + const get = (s: EditorState): DecorationSet => fn(inputs(deps, s) as I, s); + class S { s: DecorationSet = RangeSet.empty; } + class D extends S { update(u: ViewUpdate): void { this.s = get(u.state); } } + const decorations = (d: D): DecorationSet => d.s; + return enables.concat(ViewPlugin.fromClass(D, { decorations })); +} +// A Gutter is an extension that adds decorations (bullets or any kind of +// symbol) in a gutter in front of document's lines. See the CodeMirror's +// documentation on GutterMarker for further details. +export function createGutter<I extends Dict>( + deps: Dependencies<I>, + className: string, + line: (inputs: I, block: Range, view: EditorView) => GutterMarker | null +): Extension { + const enables = mapDict(deps, getExtension); + const extension = gutter({ class: className, lineMarker: (view, block) => { + return line(inputs(deps, view.state) as I, block, view); + }}); + return enables.concat(extension); +} -// ----------------------------------------------------------------------------- -// Generic plugin interface -// ----------------------------------------------------------------------------- - -// Types declarations for event handlers. It is built on the same idea as the -// ones from CodeMirror, but our handlers are conceived as purely functionnal. -export type EventMap = HTMLElementEventMap; -export type Handler<S, E> = (s: S, e: E, v: EditorView) => S | undefined; -export type Handlers<S> = { [e in keyof EventMap]?: Handler<S, EventMap[e]> }; - -// The plugin interface contains all necessary definition to build a CodeMirror -// extension. However, it should be simpler to use and define for two reasons: -// - everything is grouped under one unique interface, instead of CodeMirror -// where it is divided between the PluginValue and PluginSpec interfaces. -// - everything is intended as purely functionnal, avoiding mutable state is -// always a good idea to make the code cleaner and easier to maintain. -// -// The interface is parameterized by the type of the plugin's internal state. -// Each plugin's method will interact with this state, and modifies it, in a -// functionnal manner, if needed. -// -// The interface's methods are as follows: -// - create: instanciate the plugin internal state using the current editor -// view. It is the only mandatory function. -// - update: update the plugin's state according to a CodeMirror view update. -// - destroy: cleanup function called when the plugin's state is destroyed. -// Only useful if the state's creation is effectful. -// - decorations: returns the decorations that should be added to the code by -// CodeMirror. -// - eventHandlers: a collection of callbacks used to react to DOM events. -export interface Plugin<State> { - create: (view: EditorView) => State; - update?: (state: State, update: ViewUpdate) => State; - destroy?: (state: State) => void; - decorations?: (state: State) => DecorationSet; - eventHandlers?: Handlers<State>; -} - -// Function used to convert a Plugin into a proper CodeMirror Extension. -// It only does plumbing to match the CodeMirror API. -export function buildExtension<S>(p: Plugin<S>): Extension { - const { update: up, destroy, decorations: d } = p; - const decorations = d && ((s: State): DecorationSet => d(s.state)); - class State { - state: S; - constructor(view: EditorView) { this.state = p.create(view); } - update(v: ViewUpdate): void { if (up) this.state = up(this.state, v); } - destroy(): void { if (destroy) destroy(this.state); } - } - let eventHandlers: DOMEventHandlers<State> | undefined = undefined; - if (p.eventHandlers) { - eventHandlers = {}; - for (const [event, handler] of Object.entries(p.eventHandlers)) { - eventHandlers[event] = function(this, event, view) { - const state = handler ? handler(this.state, event, view) : undefined; - if (state) { this.state = state; view.dispatch(); return true; } - return false; - }; - } - } - return ViewPlugin.fromClass(State, { decorations, eventHandlers }); +// An Event Handler is an extention responsible of performing a computation each +// time a DOM event (like <mouseup> or <contextmenu>) happens. +export function createEventHandler<I extends Dict>( + deps: Dependencies<I>, + handlers: Handlers<I>, +): Extension { + const enables = mapDict(deps, getExtension); + const domEventHandlers = Object.fromEntries(Object.keys(handlers).map((k) => { + const h = handlers[k] as Handler<I, typeof k>; + const fn = (e: typeof k, v: EditorView) => + h(inputs(deps, v.state) as I, v, e); + return [k, fn]; + })); + return enables.concat(EditorView.domEventHandlers(domEventHandlers)); } @@ -296,6 +310,37 @@ function findMarker(tree: Tree, marker: Marker): Tree | undefined { +// ----------------------------------------------------------------------------- +// Function code representation +// ----------------------------------------------------------------------------- + +// This field contains the current function's code as represented by Ivette. +// Its set function takes care to update the CodeMirror displayed document. +const Text = createTextField(); +function createTextField(): Field<text> { + const { get, set, structure } = createField<text>(null); + const useSet: Set<text> = (view, text) => { + set(view, text); + React.useEffect(() => { + const selection = { anchor: 0 }; + const length = view?.state.doc.length; + const changes = { from: 0, to: length, insert: textToString(text) }; + view?.dispatch({ changes, selection }); + }, [view, text]); + }; + return { init: null, get, set: useSet, structure }; +} + +// This aspect computes the tree representing the currently displayed function's +// code, represented by the <Text> field. +const Tree = createAspect({ t: Text }, ({ t }) => textToTree(t) ?? leaf(0, 0)); + +// This aspect computes the markers ranges of the currently displayed function's +// tree, represented by the <Tree> aspect. +const Ranges = createAspect({ t: Tree }, ({ t }) => markersRangesOfTree(t)); + + + // ----------------------------------------------------------------------------- // Selected marker representation // ----------------------------------------------------------------------------- @@ -316,16 +361,15 @@ const UpdateSelection = createField<UpdateSelection>(() => { return; }); // select a new part of the document) and update the Ivette selection // accordingly. This will update the Marker field during the next Editor // component's render and thus update everything else. -const MarkerUpdater = EditorView.domEventHandlers({ - mouseup: (_, view) => { - const fct = Fct.get(view.state); - const tree = Tree.get(view.state); - const update = UpdateSelection.get(view.state); +const MarkerUpdater = createMarkerUpdater(); +function createMarkerUpdater(): Extension { + const deps = { fct: Fct, tree: Tree, update: UpdateSelection }; + return createEventHandler(deps, { mouseup: ({ fct, tree, update }, view) => { const main = view.state.selection.main; const id = coveringNode(tree, main.from)?.id; update({ location: { fct, marker: Ast.jMarker(id) } }); - } -}); + }}); +} @@ -344,11 +388,11 @@ const UpdateHovered = createField<UpdateHovered>(() => { return ; }); // The Hovered field is updated each time the mouse moves through the CodeMirror // document. The handlers updates the Ivette hovered information, which is then // reflected on the Hovered field by the Editor component itself. -const HoveredUpdater = EditorView.domEventHandlers({ - mousemove: (event, view) => { - const fct = Fct.get(view.state); - const tree = Tree.get(view.state); - const updateHovered = UpdateHovered.get(view.state); +const HoveredUpdater = createHoveredUpdater(); +function createHoveredUpdater(): Extension { + const deps = { fct: Fct, tree: Tree, update: UpdateHovered }; + return createEventHandler(deps, { mousemove: (inputs, view, event) => { + const { fct, tree, update: updateHovered } = inputs; const coords = { x: event.clientX, y: event.clientY }; const pos = view.posAtCoords(coords); if (!pos) return; const hov = coveringNode(tree, pos); if (!hov) return; @@ -363,54 +407,60 @@ const HoveredUpdater = EditorView.domEventHandlers({ if (!horizontallyOk || !verticallyOk) return; const marker = Ast.jMarker(hov?.id); updateHovered(marker ? { fct, marker } : undefined); - } -}); + }}); +} // ----------------------------------------------------------------------------- -// Function code representation, general information and data structures +// Plugin decorating hovered and selected elements // ----------------------------------------------------------------------------- -// This field contains the current function's code as represented by Ivette. -// Its set function takes care to update the CodeMirror displayed document. -const Text = createTextField(); -function createTextField(): Field<text> { - const { get, set, structure } = createField<text>(null); - const useSet: Set<text> = (view, text) => { - set(view, text); - React.useEffect(() => { - const selection = { anchor: 0 }; - const length = view?.state.doc.length; - const changes = { from: 0, to: length, insert: textToString(text) }; - view?.dispatch({ changes, selection }); - }, [view, text]); - }; - return { init: null, get, set: useSet, structure }; +const CodeDecorator = createCodeDecorator(); +function createCodeDecorator(): Extension { + const hoveredClass = Decoration.mark({ class: 'cm-hovered-code' }); + const selectedClass = Decoration.mark({ class: 'cm-selected-code' }); + const deps = { tree: Tree, marker: Marker, hovered: Hovered }; + return createDecorator(deps, ({ tree, marker: m, hovered: h }) => { + const selected = m && findMarker(tree, m); + const hovered = h && findMarker(tree, h); + const range = selected && selectedClass.range(selected.from, selected.to); + const add = hovered && [ hoveredClass.range(hovered.from, hovered.to) ]; + const set = range ? RangeSet.of(range) : RangeSet.empty; + return set.update({ add, sort: true }); + }); } -// This aspect computes the tree representing the currently displayed function's -// code, represented by the <Text> field. -const Tree = createAspect({ t: Text }, ({ t }) => textToTree(t) ?? leaf(0, 0)); -// This aspect computes the markers ranges of the currently displayed function's -// tree, represented by the <Tree> aspect. -const Ranges = createAspect({ t: Tree }, ({ t }) => markersRangesOfTree(t)); + +// ----------------------------------------------------------------------------- +// Dead code decorations plugin +// ----------------------------------------------------------------------------- // This field contains the dead code information as inferred by Eva. const Dead = createField<Eva.deadCode>({ unreachable: [], nonTerminating: [] }); -// This field contains all the current function's callers, as inferred by Eva. -const Callers = createField<Caller[]>([]); - -// This field contains information on markers. -type GetMarkerData = (key: string) => Ast.markerInfoData | undefined; -const GetMarkerData = createField<GetMarkerData>(() => undefined); +const DeadCodeDecorator = createDeadCodeDecorator(); +function createDeadCodeDecorator(): Extension { + const uClass = Decoration.mark({ class: 'cm-dead-code' }); + const tClass = Decoration.mark({ class: 'cm-non-term-code' }); + const deps = { dead: Dead, ranges: Ranges }; + return createDecorator(deps, ({ dead, ranges }) => { + const range = (marker: string): Range | undefined => ranges.get(marker); + function isDef<A>(a: A | undefined): a is A { return a !== undefined; } + const getRanges = (xs: string[]): Range[] => xs.map(range).filter(isDef); + const unreachableRanges = getRanges(dead.unreachable); + const unreachable = unreachableRanges.map(r => uClass.range(r.from, r.to)); + const nonTermRanges = getRanges(dead.nonTerminating); + const nonTerm = nonTermRanges.map(r => tClass.range(r.from, r.to)); + return RangeSet.of(unreachable.concat(nonTerm), true); + }); +} // ----------------------------------------------------------------------------- -// Representation of properties' information +// Property bullets extension // ----------------------------------------------------------------------------- // This field contains information on properties' tags. @@ -452,83 +502,6 @@ function createPropertiesTags(): Aspect<Map<string, States.Tag>> { }); } - - -// ----------------------------------------------------------------------------- -// Plugin decorating hovered and selected elements -// ----------------------------------------------------------------------------- - -// The different kind of decorations used in this plugin. -const hoveredClass = Decoration.mark({ class: 'cm-hovered-code' }); -const selectedClass = Decoration.mark({ class: 'cm-selected-code' }); - -// Plugin declaration. -const CodeDecorationPlugin: Plugin<DecorationSet> = { - - // There is no decoration or hovered/selected nodes in the initial state. - create: () => RangeSet.empty, - - // We do not compute the decorations here, as it seems like CodeMirror calls - // this method really often, which may be costly. We should actually benchmark - // this to be sure that it is really necessary. - decorations: (state) => state, - - // The selected nodes handling is done in this function. - update: (_, u) => { - const tree = Tree.get(u.state); - const selectedMarker = Marker.get(u.state); - const selected = selectedMarker && findMarker(tree, selectedMarker); - const hoveredMarker = Hovered.get(u.state); - const hovered = hoveredMarker && findMarker(tree, hoveredMarker); - const range = selected && selectedClass.range(selected.from, selected.to); - const add = hovered && [ hoveredClass.range(hovered.from, hovered.to) ]; - const set = range ? RangeSet.of(range) : RangeSet.empty; - return set.update({ add, sort: true }); - }, - -}; - - - -// ----------------------------------------------------------------------------- -// Dead code decorations plugin -// ----------------------------------------------------------------------------- - -// The decorations used in this plugin. -const unreachableClass = Decoration.mark({ class: 'cm-dead-code' }); -const nonTerminatingClass = Decoration.mark({ class: 'cm-non-term-code' }); - -// The plugin itself. The decorations are recomputed only once each time the -// selected function is changed. -const DeadCodePlugin: Plugin<DecorationSet> = { - create: () => RangeSet.empty, - decorations: (state) => state, - update: (state, update) => { - if (!update.docChanged) return state; - const dead = Dead.get(update.state); - const ranges = Ranges.get(update.state); - const unreachable = []; - for (const marker of dead.unreachable) { - const r = ranges.get(marker); if (!r) continue; - const range = unreachableClass.range(r.from, r.to); - unreachable.push(range); - } - const nonTerm = []; - for (const marker of dead.nonTerminating) { - const r = ranges.get(marker); if (!r) continue; - const range = nonTerminatingClass.range(r.from, r.to); - nonTerm.push(range); - } - return RangeSet.of(unreachable.concat(nonTerm), true); - }, -}; - - - -// ----------------------------------------------------------------------------- -// Property bullets extension -// ----------------------------------------------------------------------------- - // Bullet colors. function getBulletColor(status: States.Tag): string { switch (status.name) { @@ -563,15 +536,13 @@ class PropertyBullet extends GutterMarker { } } -// The properties gutter extension itself. For each line, it recovers the -// relevant markers in the code tree, retrieves the corresponding properties and -// builds the bullets. -const PropertiesGutter: Extension = gutter({ - class: 'cm-property-gutter', - lineMarker(view, block) { +const PropertiesGutter = createPropertiesGutter(); +function createPropertiesGutter(): Extension { + const deps = { ranges: PropertiesRanges, propTags: PropertiesTags }; + return createGutter(deps, 'cm-property-gutter', (inputs, block, view) => { + const { ranges, propTags } = inputs; const line = view.state.doc.lineAt(block.from); - const start = line.from; const end = line.from + block.length; - const ranges = PropertiesRanges.get(view.state); + const start = line.from; const end = line.to; const inLine = (r: Range): boolean => start <= r.from && r.to <= end; function isHeader(r: Range): boolean { if (!line.text.includes('requires')) return false; @@ -580,11 +551,10 @@ const PropertiesGutter: Extension = gutter({ } const prop = ranges.find((r) => inLine(r.range) || isHeader(r.range)); if (!prop) return null; - const propTags = PropertiesTags.get(view.state); const statusTag = propTags.get(prop.key); return statusTag ? new PropertyBullet(statusTag) : null; - } -}); + }); +} @@ -630,27 +600,34 @@ async function studia(props: StudiaProps): Promise<StudiaInfos> { // Context menu // ----------------------------------------------------------------------------- -const ContextMenu = EditorView.domEventHandlers({ - contextmenu: (event, view) => { - const tree = Tree.get(view.state); - const locations = Callers.get(view.state); - const updateSelection = UpdateSelection.get(view.state); - const getMarkerData = GetMarkerData.get(view.state); +// This field contains all the current function's callers, as inferred by Eva. +const Callers = createField<Caller[]>([]); + +// This field contains information on markers. +type GetMarkerData = (key: string) => Ast.markerInfoData | undefined; +const GetMarkerData = createField<GetMarkerData>(() => undefined); + +const ContextMenuHandler = createContextMenuHandler(); +function createContextMenuHandler(): Extension { + const data = { tree: Tree, locations: Callers }; + const deps = { ...data, update: UpdateSelection, getData: GetMarkerData }; + return createEventHandler(deps, { contextmenu: (inputs, view, event) => { + const { tree, locations, update, getData } = inputs; const coords = { x: event.clientX, y: event.clientY }; const position = view.posAtCoords(coords); if (!position) return; const node = coveringNode(tree, position); if (!node || !node.id) return; const items: Dome.PopupMenuItem[] = []; - const info = getMarkerData(node.id); + const info = getData(node.id); if (info?.var === 'function') { if (info.kind === 'declaration') { - const callers = Dictionary.groupBy(locations, e => e.fct); - Dictionary.forEach(callers, (e) => { + const callers = Lodash.groupBy(locations, e => e.fct); + Lodash.forEach(callers, (e) => { const callerName = e[0].fct; const callSites = e.length > 1 ? `(${e.length} call sites)` : ''; items.push({ label: `Go to caller ${callerName} ` + callSites, - onClick: () => updateSelection({ + onClick: () => update({ name: `Call sites of function ${info.name}`, locations: locations, index: locations.findIndex(l => l.fct === callerName) @@ -659,7 +636,7 @@ const ContextMenu = EditorView.domEventHandlers({ }); } else { const location = { fct: info.name }; - const onClick = (): void => updateSelection({ location }); + const onClick = (): void => update({ location }); const label = `Go to definition of ${info.name}`; items.push({ label, onClick }); } @@ -667,7 +644,7 @@ const ContextMenu = EditorView.domEventHandlers({ const enabled = info?.kind === 'lvalue' || info?.var === 'variable'; const onClick = (kind: access): void => { if (info && node.id) - studia({ marker: node.id, info, kind }).then(updateSelection); + studia({ marker: node.id, info, kind }).then(update); }; const reads = 'Studia: select reads'; const writes = 'Studia: select writes'; @@ -675,8 +652,8 @@ const ContextMenu = EditorView.domEventHandlers({ items.push({ label: writes, enabled, onClick: () => onClick('Writes') }); if (items.length > 0) Dome.popupMenu(items); return; - } -}); + }}); +} @@ -743,33 +720,13 @@ function useFctCallers(fct: Fct): Caller[] { // Necessary extensions for our needs. const baseExtensions: Extension[] = [ - Fct.structure.extension, - Marker.structure.extension, - UpdateSelection.structure.extension, MarkerUpdater, - - Hovered.structure.extension, - UpdateHovered.structure.extension, HoveredUpdater, - - Text.structure.extension, - Tree.extension, - Ranges.extension, - Dead.structure.extension, - Callers.structure.extension, - GetMarkerData.structure.extension, - - Tags.structure.extension, - PropertiesStatuses.structure.extension, - PropertiesRanges.extension, - PropertiesTags.extension, + CodeDecorator, + DeadCodeDecorator, + ContextMenuHandler, PropertiesGutter, foldGutter(), - - buildExtension(CodeDecorationPlugin), - buildExtension(DeadCodePlugin), - ContextMenu, - Highlight, new LanguageSupport(cppLanguage), ];