prettier format

This commit is contained in:
Kiril Videlov 2023-03-02 15:05:39 +01:00
parent 546c97c3ef
commit c087e57158
6 changed files with 285 additions and 285 deletions

View File

@ -1,11 +1,11 @@
import { Operation, type Delta } from '$lib/deltas'; import { Operation, type Delta } from '$lib/deltas';
import { import {
Text, Text,
ChangeSet, ChangeSet,
EditorState, EditorState,
EditorSelection, EditorSelection,
type ChangeSpec, type ChangeSpec,
SelectionRange SelectionRange
} from '@codemirror/state'; } from '@codemirror/state';
import { EditorView } from '@codemirror/view'; import { EditorView } from '@codemirror/view';
import { getLanguage } from './languages'; import { getLanguage } from './languages';
@ -13,135 +13,135 @@ import extensions from './extensions';
import { markChanges } from './mark'; import { markChanges } from './mark';
const toChangeSpec = (operation: Operation): ChangeSpec => { const toChangeSpec = (operation: Operation): ChangeSpec => {
if (Operation.isInsert(operation)) { if (Operation.isInsert(operation)) {
return { return {
from: operation.insert[0], from: operation.insert[0],
insert: operation.insert[1] insert: operation.insert[1]
}; };
} else if (Operation.isDelete(operation)) { } else if (Operation.isDelete(operation)) {
return { return {
from: operation.delete[0], from: operation.delete[0],
to: operation.delete[0] + operation.delete[1] to: operation.delete[0] + operation.delete[1]
}; };
} else { } else {
throw new Error(`${operation} is not supported`); throw new Error(`${operation} is not supported`);
} }
}; };
type Params = { doc: string; deltas: Delta[]; filepath: string }; type Params = { doc: string; deltas: Delta[]; filepath: string };
const makeBaseState = (doc: string, filepath: string) => { const makeBaseState = (doc: string, filepath: string) => {
const language = getLanguage(filepath); const language = getLanguage(filepath);
return EditorState.create({ return EditorState.create({
doc, doc,
extensions: language ? [...extensions, language] : extensions extensions: language ? [...extensions, language] : extensions
}); });
}; };
const toChangeSet = (deltas: Delta[], initLength: number): ChangeSet => { const toChangeSet = (deltas: Delta[], initLength: number): ChangeSet => {
const specs = deltas.flatMap(({ operations }) => operations.map(toChangeSpec)); const specs = deltas.flatMap(({ operations }) => operations.map(toChangeSpec));
const sets = specs.reduce((sets: ChangeSet[], spec) => { const sets = specs.reduce((sets: ChangeSet[], spec) => {
const set = ChangeSet.of(spec, sets.length > 0 ? sets[sets.length - 1].newLength : initLength); const set = ChangeSet.of(spec, sets.length > 0 ? sets[sets.length - 1].newLength : initLength);
return [...sets, set]; return [...sets, set];
}, [] as ChangeSet[]); }, [] as ChangeSet[]);
return sets.length > 0 ? sets.reduce((a, b) => a.compose(b)) : ChangeSet.empty(initLength); return sets.length > 0 ? sets.reduce((a, b) => a.compose(b)) : ChangeSet.empty(initLength);
}; };
const toRange = (operation: Operation) => { const toRange = (operation: Operation) => {
if (Operation.isInsert(operation)) { if (Operation.isInsert(operation)) {
const anchor = operation.insert[0]; const anchor = operation.insert[0];
const head = operation.insert[0] + operation.insert[1].length; const head = operation.insert[0] + operation.insert[1].length;
return EditorSelection.range(anchor, head); return EditorSelection.range(anchor, head);
} else if (Operation.isDelete(operation)) { } else if (Operation.isDelete(operation)) {
const anchor = operation.delete[0]; const anchor = operation.delete[0];
const head = operation.delete[0] + operation.delete[1]; const head = operation.delete[0] + operation.delete[1];
return EditorSelection.range(anchor, head); return EditorSelection.range(anchor, head);
} else { } else {
return undefined; return undefined;
} }
}; };
const toSelection = (changes: ChangeSet, delta: Delta | undefined): EditorSelection | undefined => { const toSelection = (changes: ChangeSet, delta: Delta | undefined): EditorSelection | undefined => {
if (delta === undefined) return undefined; if (delta === undefined) return undefined;
if (delta.operations.length === 0) return undefined; if (delta.operations.length === 0) return undefined;
const ranges = delta.operations const ranges = delta.operations
.map(toRange) .map(toRange)
.filter((r): r is SelectionRange => r !== undefined) .filter((r): r is SelectionRange => r !== undefined)
.filter((range) => range.head <= changes.newLength && range.anchor <= changes.newLength); .filter((range) => range.head <= changes.newLength && range.anchor <= changes.newLength);
return ranges.length ? EditorSelection.create(ranges) : undefined; return ranges.length ? EditorSelection.create(ranges) : undefined;
}; };
// this action assumes: // this action assumes:
// * that deltas list is append only. // * that deltas list is append only.
// * that each (filepath, doc) pair never changes. // * that each (filepath, doc) pair never changes.
export default (parent: HTMLElement, { doc, deltas, filepath }: Params) => { export default (parent: HTMLElement, { doc, deltas, filepath }: Params) => {
const view = new EditorView({ state: makeBaseState(doc, filepath), parent }); const view = new EditorView({ state: makeBaseState(doc, filepath), parent });
view.dispatch( view.dispatch(
view.state.update({ view.state.update({
changes: toChangeSet(deltas, doc.length) changes: toChangeSet(deltas, doc.length)
}) })
); );
let currentFilepath = filepath; let currentFilepath = filepath;
const stateCache: Record<string, EditorState> = {}; const stateCache: Record<string, EditorState> = {};
const deltasCache: Record<string, Delta[]> = {}; const deltasCache: Record<string, Delta[]> = {};
stateCache[filepath] = view.state; stateCache[filepath] = view.state;
deltasCache[filepath] = deltas; deltasCache[filepath] = deltas;
return { return {
update: ({ doc, deltas: newDeltas, filepath }: Params) => { update: ({ doc, deltas: newDeltas, filepath }: Params) => {
if (filepath !== currentFilepath) { if (filepath !== currentFilepath) {
view.setState(stateCache[filepath] ?? makeBaseState(doc, filepath)); view.setState(stateCache[filepath] ?? makeBaseState(doc, filepath));
} }
const currentDeltas = deltasCache[filepath] || []; const currentDeltas = deltasCache[filepath] || [];
if (currentDeltas.length > newDeltas.length) { if (currentDeltas.length > newDeltas.length) {
// rewind backward // rewind backward
const baseText = Text.of([doc]); const baseText = Text.of([doc]);
const targetChange = toChangeSet(newDeltas, baseText.length); const targetChange = toChangeSet(newDeltas, baseText.length);
const targetText = targetChange.apply(baseText); const targetText = targetChange.apply(baseText);
const deltasToRevert = currentDeltas.slice(newDeltas.length); const deltasToRevert = currentDeltas.slice(newDeltas.length);
const revertChange = toChangeSet(deltasToRevert, targetText.length); const revertChange = toChangeSet(deltasToRevert, targetText.length);
const changes = revertChange.invert(targetText); const changes = revertChange.invert(targetText);
const selection = toSelection(changes, newDeltas.at(-1)); const selection = toSelection(changes, newDeltas.at(-1));
view.dispatch({ view.dispatch({
changes, changes,
selection, selection,
scrollIntoView: true, scrollIntoView: true,
effects: markChanges(selection) effects: markChanges(selection)
}); });
} else { } else {
// rewind forward // rewind forward
// verify that deltas are append only // verify that deltas are append only
currentDeltas.forEach((delta, i) => { currentDeltas.forEach((delta, i) => {
if (i >= newDeltas.length) return; if (i >= newDeltas.length) return;
if (delta !== newDeltas[i]) throw new Error('deltas are not append only'); if (delta !== newDeltas[i]) throw new Error('deltas are not append only');
}); });
const deltasToApply = newDeltas.slice(currentDeltas.length); const deltasToApply = newDeltas.slice(currentDeltas.length);
const changes = toChangeSet(deltasToApply, view.state.doc.length); const changes = toChangeSet(deltasToApply, view.state.doc.length);
const selection = toSelection(changes, deltasToApply.at(-1)); const selection = toSelection(changes, deltasToApply.at(-1));
view.dispatch({ view.dispatch({
changes, changes,
selection, selection,
scrollIntoView: true, scrollIntoView: true,
effects: markChanges(selection) effects: markChanges(selection)
}); });
} }
// don't forget to update caches // don't forget to update caches
stateCache[filepath] = view.state; stateCache[filepath] = view.state;
deltasCache[filepath] = newDeltas; deltasCache[filepath] = newDeltas;
currentFilepath = filepath; currentFilepath = filepath;
}, },
destroy: () => view.destroy() destroy: () => view.destroy()
}; };
}; };

View File

@ -4,56 +4,56 @@ import { tags as t } from '@lezer/highlight';
import type { Theme } from './themes/theme'; import type { Theme } from './themes/theme';
export const colorTheme = (theme: Theme, options?: { dark: boolean }) => export const colorTheme = (theme: Theme, options?: { dark: boolean }) =>
EditorView.theme( EditorView.theme(
{ {
'&': { '&': {
color: theme.fg0, color: theme.fg0,
backgroundColor: theme.bg0 backgroundColor: theme.bg0
}, },
'.cm-gutters': { '.cm-gutters': {
color: theme.fg0, color: theme.fg0,
backgroundColor: theme.bg0, backgroundColor: theme.bg0,
border: 'none' border: 'none'
}, },
'.cm-mark': { backgroundColor: theme.bg2 } '.cm-mark': { backgroundColor: theme.bg2 }
}, },
options options
); );
export const highlightStyle = (theme: Theme) => export const highlightStyle = (theme: Theme) =>
HighlightStyle.define([ HighlightStyle.define([
{ tag: t.keyword, color: theme.red }, { tag: t.keyword, color: theme.red },
{ tag: [t.propertyName, t.name, t.deleted, t.character, t.macroName], color: theme.blue }, { tag: [t.propertyName, t.name, t.deleted, t.character, t.macroName], color: theme.blue },
{ tag: [t.function(t.variableName), t.labelName], color: theme.green, fontWeight: 'bold' }, { tag: [t.function(t.variableName), t.labelName], color: theme.green, fontWeight: 'bold' },
{ tag: [t.definition(t.name), t.separator], color: theme.yellow }, { tag: [t.definition(t.name), t.separator], color: theme.yellow },
{ {
tag: [ tag: [
t.typeName, t.typeName,
t.className, t.className,
t.number, t.number,
t.changed, t.changed,
t.annotation, t.annotation,
t.modifier, t.modifier,
t.self, t.self,
t.namespace t.namespace
], ],
color: theme.fg1 color: theme.fg1
}, },
{ {
tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
color: theme.orange, color: theme.orange,
fontStyle: 'italic' fontStyle: 'italic'
}, },
{ tag: [t.meta, t.comment], color: theme.gray, fontStyle: 'italic' }, { tag: [t.meta, t.comment], color: theme.gray, fontStyle: 'italic' },
{ tag: t.strong, fontWeight: 'bold' }, { tag: t.strong, fontWeight: 'bold' },
{ tag: t.emphasis, fontStyle: 'italic' }, { tag: t.emphasis, fontStyle: 'italic' },
{ tag: t.strikethrough, textDecoration: 'line-through' }, { tag: t.strikethrough, textDecoration: 'line-through' },
{ tag: t.heading, fontWeight: 'bold', color: theme.blue }, { tag: t.heading, fontWeight: 'bold', color: theme.blue },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: theme.purple }, { tag: [t.atom, t.bool, t.special(t.variableName)], color: theme.purple },
{ {
tag: [t.processingInstruction, t.string, t.inserted], tag: [t.processingInstruction, t.string, t.inserted],
color: theme.green, color: theme.green,
fontStyle: 'italic' fontStyle: 'italic'
}, },
{ tag: t.invalid, color: theme.fg0 } { tag: t.invalid, color: theme.fg0 }
]); ]);

View File

@ -6,15 +6,15 @@ import { gruvbox } from './themes';
const theme = gruvbox.dark; const theme = gruvbox.dark;
const sizes = EditorView.theme({ const sizes = EditorView.theme({
'&': { height: '100%', width: '100%' }, '&': { height: '100%', width: '100%' },
'.cm-scroller': { overflow: 'scroll' } '.cm-scroller': { overflow: 'scroll' }
}); });
export default [ export default [
colorTheme(theme), // set color theme colorTheme(theme), // set color theme
syntaxHighlighting(highlightStyle(theme)), // highlight syntax syntaxHighlighting(highlightStyle(theme)), // highlight syntax
EditorView.editable.of(false), // disable editing EditorView.editable.of(false), // disable editing
EditorView.lineWrapping, // wrap lines EditorView.lineWrapping, // wrap lines
lineNumbers(), // show line numbers lineNumbers(), // show line numbers
sizes // set size sizes // set size
]; ];

View File

@ -2,28 +2,28 @@ import { EditorView, Decoration, type DecorationSet } from '@codemirror/view';
import { StateField, StateEffect, EditorSelection, RangeSet } from '@codemirror/state'; import { StateField, StateEffect, EditorSelection, RangeSet } from '@codemirror/state';
const addMark = StateEffect.define<{ from: number; to: number }>({ const addMark = StateEffect.define<{ from: number; to: number }>({
map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) }) map: ({ from, to }, change) => ({ from: change.mapPos(from), to: change.mapPos(to) })
}); });
const mark = Decoration.mark({ class: 'cm-mark' }); const mark = Decoration.mark({ class: 'cm-mark' });
const changes = StateField.define<DecorationSet>({ const changes = StateField.define<DecorationSet>({
create: () => Decoration.none, create: () => Decoration.none,
update: (_old, transaction) => update: (_old, transaction) =>
RangeSet.of( RangeSet.of(
transaction.effects transaction.effects
.filter((effect) => effect.is(addMark)) .filter((effect) => effect.is(addMark))
.map((effect) => mark.range(effect.value.from, effect.value.to)) .map((effect) => mark.range(effect.value.from, effect.value.to))
), ),
provide: (field) => EditorView.decorations.from(field) provide: (field) => EditorView.decorations.from(field)
}); });
export const markChanges = (selection: EditorSelection | undefined) => { export const markChanges = (selection: EditorSelection | undefined) => {
if (selection === undefined) return undefined; if (selection === undefined) return undefined;
const effects: StateEffect<unknown>[] = selection.ranges const effects: StateEffect<unknown>[] = selection.ranges
.filter((r) => !r.empty) .filter((r) => !r.empty)
.map(({ from, to }) => addMark.of({ from, to })); .map(({ from, to }) => addMark.of({ from, to }));
return effects.length > 0 ? [...effects, StateEffect.appendConfig.of([changes])] : undefined; return effects.length > 0 ? [...effects, StateEffect.appendConfig.of([changes])] : undefined;
}; };

View File

@ -1,94 +1,94 @@
import type { Theme } from './theme'; import type { Theme } from './theme';
const palette = { const palette = {
dark0Hard: '#1d2021', dark0Hard: '#1d2021',
dark0: '#282828', dark0: '#282828',
dark0Soft: '#32302f', dark0Soft: '#32302f',
dark1: '#3c3836', dark1: '#3c3836',
dark2: '#504945', dark2: '#504945',
dark3: '#665c54', dark3: '#665c54',
dark4: '#7c6f64', dark4: '#7c6f64',
light0Hard: '#f9f5d7', light0Hard: '#f9f5d7',
light0: '#fbf1c7', light0: '#fbf1c7',
light0Soft: '#f2e5bc', light0Soft: '#f2e5bc',
light1: '#ebdbb2', light1: '#ebdbb2',
light2: '#d5c4a1', light2: '#d5c4a1',
light3: '#bdae93', light3: '#bdae93',
light4: '#a89984', light4: '#a89984',
brightRed: '#fb4934', brightRed: '#fb4934',
brightGreen: '#b8bb26', brightGreen: '#b8bb26',
brightYellow: '#fabd2f', brightYellow: '#fabd2f',
brightBlue: '#83a598', brightBlue: '#83a598',
brightPurple: '#d3869b', brightPurple: '#d3869b',
brightAqua: '#8ec07c', brightAqua: '#8ec07c',
brightOrange: '#fe8019', brightOrange: '#fe8019',
neutralRed: '#cc241d', neutralRed: '#cc241d',
neutralGreen: '#98971a', neutralGreen: '#98971a',
neutralYellow: '#d79921', neutralYellow: '#d79921',
neutralBlue: '#458588', neutralBlue: '#458588',
neutralPurple: '#b16286', neutralPurple: '#b16286',
neutralAqua: '#689d6a', neutralAqua: '#689d6a',
neutralOrange: '#d65d0e', neutralOrange: '#d65d0e',
fadedRed: '#9d0006', fadedRed: '#9d0006',
fadedGreen: '#79740e', fadedGreen: '#79740e',
fadedYellow: '#b57614', fadedYellow: '#b57614',
fadedBlue: '#076678', fadedBlue: '#076678',
fadedPurple: '#8f3f71', fadedPurple: '#8f3f71',
fadedAqua: '#427b58', fadedAqua: '#427b58',
fadedOrange: '#af3a03', fadedOrange: '#af3a03',
gray: '#928374' gray: '#928374'
}; };
export const dark: Theme = { export const dark: Theme = {
bg0: palette.dark0, bg0: palette.dark0,
bg1: palette.dark1, bg1: palette.dark1,
bg2: palette.dark2, bg2: palette.dark2,
bg3: palette.dark3, bg3: palette.dark3,
bg4: palette.dark4, bg4: palette.dark4,
fg0: palette.light0, fg0: palette.light0,
fg1: palette.light1, fg1: palette.light1,
fg2: palette.light2, fg2: palette.light2,
fg3: palette.light3, fg3: palette.light3,
fg4: palette.light4, fg4: palette.light4,
red: palette.brightRed, red: palette.brightRed,
green: palette.brightGreen, green: palette.brightGreen,
yellow: palette.brightYellow, yellow: palette.brightYellow,
blue: palette.brightBlue, blue: palette.brightBlue,
purple: palette.brightPurple, purple: palette.brightPurple,
aqua: palette.brightAqua, aqua: palette.brightAqua,
orange: palette.brightOrange, orange: palette.brightOrange,
neutralRed: palette.neutralRed, neutralRed: palette.neutralRed,
neutralGreen: palette.neutralGreen, neutralGreen: palette.neutralGreen,
neutralYellow: palette.neutralYellow, neutralYellow: palette.neutralYellow,
neutralBlue: palette.neutralBlue, neutralBlue: palette.neutralBlue,
neutralPurple: palette.neutralPurple, neutralPurple: palette.neutralPurple,
neutralAqua: palette.neutralAqua, neutralAqua: palette.neutralAqua,
gray: palette.gray gray: palette.gray
}; };
export const light: Theme = { export const light: Theme = {
bg0: palette.light0, bg0: palette.light0,
bg1: palette.light1, bg1: palette.light1,
bg2: palette.light2, bg2: palette.light2,
bg3: palette.light3, bg3: palette.light3,
bg4: palette.light4, bg4: palette.light4,
fg0: palette.dark0, fg0: palette.dark0,
fg1: palette.dark1, fg1: palette.dark1,
fg2: palette.dark2, fg2: palette.dark2,
fg3: palette.dark3, fg3: palette.dark3,
fg4: palette.dark4, fg4: palette.dark4,
red: palette.fadedRed, red: palette.fadedRed,
green: palette.fadedGreen, green: palette.fadedGreen,
yellow: palette.fadedYellow, yellow: palette.fadedYellow,
blue: palette.fadedBlue, blue: palette.fadedBlue,
purple: palette.fadedPurple, purple: palette.fadedPurple,
aqua: palette.fadedAqua, aqua: palette.fadedAqua,
orange: palette.fadedOrange, orange: palette.fadedOrange,
neutralRed: palette.neutralRed, neutralRed: palette.neutralRed,
neutralGreen: palette.neutralGreen, neutralGreen: palette.neutralGreen,
neutralYellow: palette.neutralYellow, neutralYellow: palette.neutralYellow,
neutralBlue: palette.neutralBlue, neutralBlue: palette.neutralBlue,
neutralPurple: palette.neutralPurple, neutralPurple: palette.neutralPurple,
neutralAqua: palette.neutralAqua, neutralAqua: palette.neutralAqua,
gray: palette.gray gray: palette.gray
}; };

View File

@ -1,28 +1,28 @@
export type Color = string; export type Color = string;
export type Theme = { export type Theme = {
bg0: Color; bg0: Color;
bg1: Color; bg1: Color;
bg2: Color; bg2: Color;
bg3: Color; bg3: Color;
bg4: Color; bg4: Color;
fg0: Color; fg0: Color;
fg1: Color; fg1: Color;
fg2: Color; fg2: Color;
fg3: Color; fg3: Color;
fg4: Color; fg4: Color;
red: Color; red: Color;
green: Color; green: Color;
yellow: Color; yellow: Color;
blue: Color; blue: Color;
purple: Color; purple: Color;
aqua: Color; aqua: Color;
orange: Color; orange: Color;
neutralRed: Color; neutralRed: Color;
neutralGreen: Color; neutralGreen: Color;
neutralYellow: Color; neutralYellow: Color;
neutralBlue: Color; neutralBlue: Color;
neutralPurple: Color; neutralPurple: Color;
neutralAqua: Color; neutralAqua: Color;
gray: Color; gray: Color;
}; };