base of autocompletion

This commit is contained in:
2023-09-08 16:35:05 +02:00
parent d2161eb5bc
commit 4b9afb46a5
2 changed files with 312 additions and 43 deletions

View File

@ -1,11 +1,13 @@
import { inlineHoveringTips } from "./documentation/inlineHelp";
import { import {
keymap, keymap,
highlightSpecialChars, highlightSpecialChars,
drawSelection, drawSelection,
highlightActiveLine, highlightActiveLine,
dropCursor, dropCursor,
rectangularSelection, // rectangularSelection,
crosshairCursor, // crosshairCursor,
highlightActiveLineGutter, highlightActiveLineGutter,
} from "@codemirror/view"; } from "@codemirror/view";
import { Extension, EditorState } from "@codemirror/state"; import { Extension, EditorState } from "@codemirror/state";
@ -24,45 +26,6 @@ import {
} from "@codemirror/autocomplete"; } from "@codemirror/autocomplete";
import { lintKeymap } from "@codemirror/lint"; 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 = (() => [ export const editorSetup: Extension = (() => [
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightSpecialChars(), highlightSpecialChars(),
@ -75,10 +38,11 @@ export const editorSetup: Extension = (() => [
bracketMatching(), bracketMatching(),
closeBrackets(), closeBrackets(),
autocompletion(), autocompletion(),
rectangularSelection(), // rectangularSelection(),
crosshairCursor(), // crosshairCursor(),
highlightActiveLine(), highlightActiveLine(),
highlightSelectionMatches(), highlightSelectionMatches(),
inlineHoveringTips,
keymap.of([ keymap.of([
...closeBracketsKeymap, ...closeBracketsKeymap,
...defaultKeymap, ...defaultKeymap,

View File

@ -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 <i>n</i> 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 <i>n</i> 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 <i>n</i> 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 <code>sound<code>.",
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 <code>sound()</code> 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 = `
<h1 class="text-orange-300 text-base pb-1">${completion.name} [<em class="text-white">${completion.category}</em>]</h1>
<p class="text-base pl-4">${completion.description}</p>
<pre class="-mt-2"><code class="pl-4 text-base">${completion.example}</code></pre></div>
`;
let dom = document.createElement("div");
dom.classList.add("px-4", "py-2", "bg-neutral-700", "rounded-lg");
dom.innerHTML = divContent;
return { dom };
},
};
}
);