121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { createMemo, createSignal } from 'solid-js';
|
|
import { sourceText } from 'source-region';
|
|
import type { CodePointSpan, SourceRegion, SourceText } from 'source-region';
|
|
import { parseDocument, programOf } from '../parser';
|
|
import type { ConcreteSyntaxResult, PartialConcreteSyntax } from '../parser';
|
|
import type { ParseError } from '../parse_errors';
|
|
import { spanLabel } from './format';
|
|
import { PaneHeader, PaneSplitter } from './Pane';
|
|
import { SourceGrid } from './SourceGrid';
|
|
import type { SourceGridAnnotation } from './SourceGrid';
|
|
import { StructureTree } from './SyntaxPane';
|
|
import type { HoverTarget } from './types';
|
|
|
|
type ParsedDocument = {
|
|
source: SourceText;
|
|
region: SourceRegion;
|
|
syntax: ConcreteSyntaxResult;
|
|
program: PartialConcreteSyntax;
|
|
errors: ParseError[];
|
|
};
|
|
|
|
const SAMPLE_INPUT = `(define square (_ x) (mul x x))
|
|
|
|
[add, 1, 2]
|
|
|
|
(define pyth (_ x y) (+ (square x) (square y)))
|
|
|
|
foo ) @@@ (bar 1)
|
|
(nested [list, 123, abc_9, name-with-dash])
|
|
[a, b c, d]
|
|
123fasd`;
|
|
|
|
export function App() {
|
|
const [input, setInput] = createSignal(SAMPLE_INPUT);
|
|
const [hovered, setHovered] = createSignal<HoverTarget | undefined>();
|
|
const [leftWidth, setLeftWidth] = createSignal(420);
|
|
const [middleWidth, setMiddleWidth] = createSignal(420);
|
|
|
|
const parsed = createMemo<ParsedDocument>(() => {
|
|
const source = sourceText(input());
|
|
const region = source.fullRegion();
|
|
const result = parseDocument(region);
|
|
return { source, region, syntax: result.syntax, program: programOf(result.syntax), errors: result.errors };
|
|
});
|
|
|
|
return (
|
|
<main
|
|
class="app-shell"
|
|
style={{
|
|
"--left-width": `${leftWidth()}px`,
|
|
"--middle-width": `${middleWidth()}px`,
|
|
}}
|
|
>
|
|
<section class="pane input-pane">
|
|
<PaneHeader title="Source" detail={`${input().length} UTF-16 units`} />
|
|
<textarea
|
|
class="source-input"
|
|
spellcheck={false}
|
|
value={input()}
|
|
onInput={(event) => {
|
|
setInput(event.currentTarget.value);
|
|
setHovered(undefined);
|
|
}}
|
|
/>
|
|
</section>
|
|
|
|
<PaneSplitter
|
|
label="Resize source and structure panes"
|
|
onDrag={(delta) => {
|
|
setLeftWidth((width) => clamp(width + delta, 280, 760));
|
|
}}
|
|
/>
|
|
|
|
<section class="pane structure-pane">
|
|
<PaneHeader
|
|
title="Structure"
|
|
detail={`${parsed().syntax.tag}, ${parsed().program.expressions.length} expressions, ${parsed().errors.length} errors`}
|
|
/>
|
|
<StructureTree
|
|
program={parsed().program}
|
|
isValid={parsed().syntax.tag === "valid"}
|
|
errorCount={parsed().errors.length}
|
|
onHover={setHovered}
|
|
/>
|
|
</section>
|
|
|
|
<PaneSplitter
|
|
label="Resize structure and source grid panes"
|
|
onDrag={(delta) => {
|
|
setMiddleWidth((width) => clamp(width + delta, 260, 760));
|
|
}}
|
|
/>
|
|
|
|
<section class="pane source-pane">
|
|
<PaneHeader
|
|
title="Source Grid"
|
|
detail={hovered() ? spanLabel(hovered()!.span) : "nothing hovered"}
|
|
/>
|
|
<SourceGrid
|
|
source={parsed().source}
|
|
region={parsed().region}
|
|
annotations={hovered() ? [hoverAnnotation(hovered()!)] : []}
|
|
/>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
function hoverAnnotation(target: HoverTarget): SourceGridAnnotation {
|
|
return {
|
|
id: "hovered",
|
|
span: target.span,
|
|
label: target.label,
|
|
cellClass: "annotation-hovered",
|
|
markerClass: "annotation-hovered-marker",
|
|
};
|
|
}
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|