From 4b9afb46a5c36b1dc899223cde2b089d1c7dce18 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Fri, 8 Sep 2023 16:35:05 +0200 Subject: [PATCH] base of autocompletion --- src/EditorSetup.ts | 50 +----- src/documentation/inlineHelp.ts | 305 ++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 43 deletions(-) create mode 100644 src/documentation/inlineHelp.ts diff --git a/src/EditorSetup.ts b/src/EditorSetup.ts index fb7ce23..28f73b9 100644 --- a/src/EditorSetup.ts +++ b/src/EditorSetup.ts @@ -1,11 +1,13 @@ +import { inlineHoveringTips } from "./documentation/inlineHelp"; + import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, dropCursor, - rectangularSelection, - crosshairCursor, + // rectangularSelection, + // crosshairCursor, highlightActiveLineGutter, } from "@codemirror/view"; import { Extension, EditorState } from "@codemirror/state"; @@ -24,45 +26,6 @@ import { } from "@codemirror/autocomplete"; import { lintKeymap } from "@codemirror/lint"; -// (The superfluous function calls around the list of extensions work -// around current limitations in tree-shaking software.) - -/// This is an extension value that just pulls together a number of -/// extensions that you might want in a basic editor. It is meant as a -/// convenient helper to quickly set up CodeMirror without installing -/// and importing a lot of separate packages. -/// -/// Specifically, it includes... -/// -/// - [the default command bindings](#commands.defaultKeymap) -/// - [line numbers](#view.lineNumbers) -/// - [special character highlighting](#view.highlightSpecialChars) -/// - [the undo history](#commands.history) -/// - [a fold gutter](#language.foldGutter) -/// - [custom selection drawing](#view.drawSelection) -/// - [drop cursor](#view.dropCursor) -/// - [multiple selections](#state.EditorState^allowMultipleSelections) -/// - [reindentation on input](#language.indentOnInput) -/// - [the default highlight style](#language.defaultHighlightStyle) (as fallback) -/// - [bracket matching](#language.bracketMatching) -/// - [bracket closing](#autocomplete.closeBrackets) -/// - [autocompletion](#autocomplete.autocompletion) -/// - [rectangular selection](#view.rectangularSelection) and [crosshair cursor](#view.crosshairCursor) -/// - [active line highlighting](#view.highlightActiveLine) -/// - [active line gutter highlighting](#view.highlightActiveLineGutter) -/// - [selection match highlighting](#search.highlightSelectionMatches) -/// - [search](#search.searchKeymap) -/// - [linting](#lint.lintKeymap) -/// -/// (You'll probably want to add some language package to your setup -/// too.) -/// -/// This extension does not allow customization. The idea is that, -/// once you decide you want to configure your editor more precisely, -/// you take this package's source (which is just a bunch of imports -/// and an array literal), copy it into your own code, and adjust it -/// as desired. - export const editorSetup: Extension = (() => [ highlightActiveLineGutter(), highlightSpecialChars(), @@ -75,10 +38,11 @@ export const editorSetup: Extension = (() => [ bracketMatching(), closeBrackets(), autocompletion(), - rectangularSelection(), - crosshairCursor(), + // rectangularSelection(), + // crosshairCursor(), highlightActiveLine(), highlightSelectionMatches(), + inlineHoveringTips, keymap.of([ ...closeBracketsKeymap, ...defaultKeymap, diff --git a/src/documentation/inlineHelp.ts b/src/documentation/inlineHelp.ts new file mode 100644 index 0000000..4657499 --- /dev/null +++ b/src/documentation/inlineHelp.ts @@ -0,0 +1,305 @@ +import { hoverTooltip } from "@codemirror/view"; +import { type EditorView } from "@codemirror/view"; + +interface InlineCompletion { + name: string; + category: string; + description: string; + example: string; +} + +type CompletionDatabase = { + [key: string]: InlineCompletion; +}; + +const completionDatabase: CompletionDatabase = { + attack: { + name: "attack", + category: "synthesis", + description: "ADSR envelope attack time (in seconds)", + example: "sound('sawtooth').attack(.5).out()", + }, + decay: { + name: "decay", + category: "synthesis", + description: "ADSR envelope decay time (in seconds)", + example: "sound('sawtooth').decay(.5).out()", + }, + sustain: { + name: "sustain", + category: "synthesis", + description: "ADSR envelope sustain level (0-1)", + example: "sound('sawtooth').sustain(.5).out()", + }, + release: { + name: "release", + category: "synthesis", + description: "ADSR envelope release time (in seconds)", + example: "sound('sawtooth').release(.5).out()", + }, + fmi: { + name: "fmi", + category: "audio", + description: "FM synth modulator index", + example: "sound('fm').fmi([1,2].beat()).out()", + }, + fmh: { + name: "fmh", + category: "audio", + description: "FM synth modulator ratio", + example: "sound('fm').fmi(2).fmh(2).out()", + }, + repeatAll: { + name: "repeatAll", + category: "patterns", + description: "Repeat every array elements n times", + example: "[0,1,2,3].repeatAll(2)", + }, + quant: { + name: "quant", + category: "functions", + description: "Quantize a value in the given array", + example: "quant(30, [0,1,2,3])", + }, + log: { + name: "log", + category: "javascript", + description: "Log a value in the console", + example: "log('Hello, world')", + }, + div: { + name: "div", + category: "patterns", + description: + "Returns next value every n beats or true and false alternatively", + example: "div(4, 50) // 2 beats of true, 2 beats of false, 50/50.", + }, + n: { + name: "n", + category: "audio", + description: "Sample number or synth oscillator partials count", + example: "sound('dr').n([1,2].beat()).out()", + }, + note: { + name: "note", + category: "patterns", + description: "MIDI note number (0-127)", + example: "sound('jvbass').note(50).out()", + }, + vel: { + name: "vel", + category: "audio", + description: "Velocity or sound volume (0-1)", + example: "sound('cp').vel(.5).out()", + }, + palindrome: { + name: "palindrome", + category: "patterns", + description: "Returns palindrome of the current array", + example: "[0,1,2,3].palindrome()", + }, + cutoff: { + name: "cutoff", + category: "filter", + description: "Lowpass filter cutoff frequency", + example: "sound('cp').cutoff(1000).out()", + }, + speed: { + name: "speed", + category: "sampling", + description: "Sample playback speed", + example: "sound('cp').speed(.5).out()", + }, + delay: { + name: "delay", + category: "effect", + description: "Delay effect dry/wet", + example: "sound('cp').delay(.5).out()", + }, + delayfb: { + name: "delayfb", + category: "effect", + description: "Delay effect feedback amount (0-1)", + example: "sound('cp').delay(0.2).delayfb(.5).out()", + }, + delaytime: { + name: "delaytime", + category: "effect", + description: "Delay effect delay time (in seconds)", + example: "sound('cp').delay(0.2).delaytime(.5).out()", + }, + gain: { + name: "gain", + category: "audio", + description: "Playback volume", + example: "sound('cp').gain(.5).out()", + }, + bar: { + name: "bar", + category: "patterns", + description: "Returns list index for the current bar (with wrapping)", + example: "[0,1,2,3].bar()", + }, + beat: { + name: "beat", + category: "patterns", + description: "Returns list index for the current beat (with wrapping)", + example: "[0,1,2,3].beat()", + }, + room: { + name: "room", + category: "effect", + description: "Reverb effect room amount", + example: "sound('cp').room(.5).out()", + }, + size: { + name: "size", + category: "effect", + description: "Reverb effect room size", + example: "sound('cp').size(.5).out()", + }, + usine: { + name: "usine", + category: "modulation", + description: "Unipolar sinusoïdal low-frequency oscillator", + example: "usine(5) // 5 hz oscillation", + }, + sine: { + name: "usine", + category: "modulation", + description: "Sinusoïdal low-frequency oscillator", + example: "usine(5) // 5 hz oscillation", + }, + utriangle: { + name: "utriangle", + category: "modulation", + description: "Unipolar triangular low-frequency oscillator", + example: "utriangle(5) // 5 hz oscillation", + }, + triangle: { + name: "triangle", + category: "modulation", + description: "Triangular low-frequency oscillator", + example: "triangle(5) // 5 hz oscillation", + }, + usaw: { + name: "usaw", + category: "modulation", + description: "Unipolar sawtooth low-frequency oscillator", + example: "usaw(5) // 5 hz oscillation", + }, + saw: { + name: "saw", + category: "modulation", + description: "Sawtooth low-frequency oscillator", + example: "saw(5) // 5 hz oscillation", + }, + square: { + name: "square", + category: "modulation", + description: "Square low-frequency oscillator", + example: "square(5) // 5 hz oscillation", + }, + usquare: { + name: "usquare", + category: "modulation", + description: "Unipolar square low-frequency oscillator", + example: "usquare(5) // 5 hz oscillation", + }, + rhythm: { + name: "rhythm", + category: "rhythm", + description: "Variant of the euclidian algorithm function", + example: "rhythm(.5, 3, 8) // time, pulses, steps", + }, + let: { + name: "let", + category: "javascript", + description: "Variable assignation", + example: "let baba = 10", + }, + mod: { + name: "mod", + category: "rhythm", + description: "return true every n pulsations.", + example: "mod(1) :: log(rand(1,5))", + }, + rand: { + name: "rand", + category: "randomness", + description: "random floating point number between x and y", + example: "rand(1, 10) // between 1 and 10", + }, + irand: { + name: "irand", + category: "randomness", + description: "random integer number between x and y", + example: "irand(1, 10) // between 1 and 10", + }, + pick: { + name: "pick", + category: "randomness", + description: "Pick a value in the given array", + example: "[1,4,10].pick()", + }, + sound: { + name: "sound", + category: "audio", + description: "Base function to play audio (samples / synths)", + example: "sound('bd').out()", + }, + snd: { + name: "snd", + category: "audio", + description: + "Base function to play audio (samples / synths). Alias for sound.", + example: "sound('bd').out()", + }, + bpm: { + name: "bpm", + category: "time", + description: "Get or set the current beats per minute.", + example: "bpm(135) // set the bpm to 135", + }, + out: { + name: "out", + category: "audio", + description: "Connect the sound() chain to the output", + example: "sound('clap').out()", + }, +}; + +export const inlineHoveringTips = hoverTooltip( + (view: any, pos: any, side: any) => { + let { from, to, text } = view.state.doc.lineAt(pos); + let start = pos, + end = pos; + while (start > from && /\w/.test(text[start - from - 1])) start--; + while (end < to && /\w/.test(text[end - from])) end++; + if ((start == pos && side < 0) || (end == pos && side > 0)) return null; + return { + pos: start, + end, + above: true, + create(view: EditorView) { + if ( + text.slice(start - from, end - from) in completionDatabase === + false + ) { + return { dom: document.createElement("div") }; + } + let completion = + completionDatabase[text.slice(start - from, end - from)] || {}; + let divContent = ` +

${completion.name} [${completion.category}]

+

${completion.description}

+
${completion.example}
+ `; + let dom = document.createElement("div"); + dom.classList.add("px-4", "py-2", "bg-neutral-700", "rounded-lg"); + dom.innerHTML = divContent; + return { dom }; + }, + }; + } +);