Skip to content

Commit 4ce001a

Browse files
committed
bidirectional marking, expand on click
1 parent 27a953c commit 4ce001a

File tree

5 files changed

+83
-69
lines changed

5 files changed

+83
-69
lines changed

packages/editor/src/lib/Workspace.svelte.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export class Workspace {
113113
#files = $state.raw<Item[]>([]);
114114
#current = $state.raw() as File;
115115

116+
#handlers = {
117+
hover: new Set<(pos: number) => void>(),
118+
select: new Set<(from: number, to: number) => void>()
119+
};
120+
116121
#onupdate: (file: File) => void;
117122
#onreset: (items: Item[]) => void;
118123

@@ -296,6 +301,22 @@ export class Workspace {
296301
this.#files = this.#files.slice(0, to_index).concat(from).concat(this.#files.slice(to_index));
297302
}
298303

304+
onhover(fn: (pos: number) => void) {
305+
this.#handlers.hover.add(fn);
306+
307+
return () => {
308+
this.#handlers.hover.delete(fn);
309+
};
310+
}
311+
312+
onselect(fn: (from: number, to: number) => void) {
313+
this.#handlers.select.add(fn);
314+
315+
return () => {
316+
this.#handlers.select.delete(fn);
317+
};
318+
}
319+
299320
remove(item: Item) {
300321
const index = this.#files.indexOf(item);
301322

@@ -474,9 +495,9 @@ export class Workspace {
474495
EditorState.readOnly.of(this.#readonly),
475496
EditorView.editable.of(!this.#readonly),
476497
EditorView.updateListener.of((update) => {
477-
if (update.docChanged) {
478-
const state = this.#view!.state!;
498+
const state = this.#view!.state!;
479499

500+
if (update.docChanged) {
480501
this.#update_file({
481502
...this.#current,
482503
contents: state.doc.toString()
@@ -485,6 +506,26 @@ export class Workspace {
485506
// preserve undo/redo across files
486507
this.states.set(this.#current.name, state);
487508
}
509+
510+
if (update.selectionSet) {
511+
if (state.selection.ranges.length === 1) {
512+
for (const handler of this.#handlers.select) {
513+
const { from, to } = state.selection.ranges[0];
514+
handler(from, to);
515+
}
516+
}
517+
}
518+
}),
519+
EditorView.domEventObservers({
520+
mousemove: (event, view) => {
521+
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
522+
523+
if (pos !== null) {
524+
for (const handler of this.#handlers.hover) {
525+
handler(pos);
526+
}
527+
}
528+
}
488529
})
489530
];
490531

packages/editor/src/lib/codemirror.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@
306306
}
307307

308308
.highlight {
309-
background: hsl(60, 100%, 80%);
309+
background: var(--sk-bg-highlight);
310310
padding: 4px 0;
311311
}
312312
}

packages/repl/src/lib/Output/AstNode.svelte

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,72 +17,49 @@
1717
1818
let { key = '', value, path_nodes = [], autoscroll = true, onhover, depth = 0 }: Props = $props();
1919
20-
const { toggleable } = get_repl_context();
20+
const { toggleable, workspace } = get_repl_context();
2121
2222
let root = depth === 0;
2323
let open = $state(root);
2424
25-
let list_item_el = $state() as HTMLLIElement;
25+
let li: HTMLLIElement;
2626
2727
let is_leaf = $derived(path_nodes[path_nodes.length - 1] === value);
28+
let is_marked = $derived(!root && path_nodes.includes(value));
29+
2830
let is_array = $derived(Array.isArray(value));
2931
let is_primitive = $derived(value === null || typeof value !== 'object');
30-
let is_markable = $derived(
31-
!is_primitive &&
32-
'start' in value &&
33-
'end' in value &&
34-
typeof value.start === 'number' &&
35-
typeof value.end === 'number'
36-
);
3732
let key_text = $derived(key ? `${key}:` : '');
3833
3934
$effect(() => {
40-
open = path_nodes.includes(value);
41-
});
35+
if (typeof value === 'object' && value !== null) {
36+
const offselect = workspace.onselect((from, to) => {
37+
const nodes = value.type === 'Fragment' ? value.nodes : is_array ? value : [value];
4238
43-
$effect(() => {
44-
if (autoscroll && is_leaf && !$toggleable) {
45-
// wait for all nodes to render before scroll
46-
tick().then(() => {
47-
if (list_item_el) {
48-
list_item_el.scrollIntoView();
39+
const start = nodes[0]?.start;
40+
const end = nodes[nodes.length - 1]?.end;
41+
42+
if (typeof start !== 'number' || typeof end !== 'number') {
43+
return;
4944
}
50-
});
51-
}
52-
});
5345
54-
function handle_mark_text(e: MouseEvent | FocusEvent) {
55-
if (is_markable) {
56-
e.stopPropagation();
57-
58-
if (
59-
'start' in value &&
60-
'end' in value &&
61-
typeof value.start === 'number' &&
62-
typeof value.end === 'number'
63-
) {
64-
// TODO
65-
// $module_editor?.markText({ from: value.start ?? 0, to: value.end ?? 0 });
66-
}
67-
}
68-
}
46+
// if node contains the current selection, open
47+
if (start <= from && end >= to) {
48+
open = true;
49+
tick().then(() => {
50+
li.scrollIntoView();
51+
});
52+
}
53+
});
6954
70-
function handle_unmark_text(e: MouseEvent) {
71-
if (is_markable) {
72-
e.stopPropagation();
73-
// TODO
74-
// $module_editor?.unmarkText();
55+
return () => {
56+
offselect();
57+
};
7558
}
76-
}
59+
});
7760
</script>
7861

79-
<li
80-
bind:this={list_item_el}
81-
class:marked={!root && is_leaf}
82-
onmouseover={handle_mark_text}
83-
onfocus={handle_mark_text}
84-
onmouseleave={handle_unmark_text}
85-
>
62+
<li bind:this={li} data-marked={is_marked} data-leaf={is_leaf}>
8663
{#if is_primitive || (is_array && value.length === 0)}
8764
<span class="value">
8865
{#if key_text}
@@ -153,8 +130,9 @@
153130
list-style-type: none;
154131
}
155132
156-
.marked {
157-
background-color: var(--sk-highlight-color);
133+
[data-marked='true']:not(:has(> [open])),
134+
[data-leaf='true'] {
135+
background-color: var(--sk-bg-highlight);
158136
}
159137
160138
summary {

packages/repl/src/lib/Output/AstView.svelte

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@
1515
1616
let { workspace, ast, autoscroll = true }: Props = $props();
1717
18-
// $cursor_index may go over the max since ast computation is usually slower.
19-
// clamping this helps prevent the collapse view flashing
20-
// TODO reimplement
21-
let max_cursor_index = 0;
22-
// $: max_cursor_index = !ast ? $cursorIndex : Math.min($cursorIndex, get_ast_max_end(ast));
18+
let cursor = $state(0);
2319
24-
let path_nodes = $derived(find_deepest_path(max_cursor_index, [ast]) || []);
20+
let path_nodes = $derived(find_deepest_path(cursor, [ast]) || []);
2521
2622
function find_deepest_path(cursor: number, paths: Ast[]): Ast[] | undefined {
2723
const value = paths[paths.length - 1];
@@ -47,17 +43,15 @@
4743
}
4844
}
4945
50-
function get_ast_max_end(ast: Ast) {
51-
let max_end = 0;
46+
$effect(() => {
47+
const offhover = workspace.onhover((pos) => {
48+
cursor = pos;
49+
});
5250
53-
for (const node of Object.values(ast) as any[]) {
54-
if (node && typeof node.end === 'number' && node.end > max_end) {
55-
max_end = node.end;
56-
}
57-
}
58-
59-
return max_end;
60-
}
51+
return () => {
52+
offhover();
53+
};
54+
});
6155
</script>
6256

6357
<div class="ast-view">

packages/site-kit/src/lib/styles/tokens/colours.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
--sk-bg-4: hsl(0, 0%, 95%); /* hover states and highlights */
2323
--sk-bg-accent: var(--sk-fg-accent);
2424
--sk-bg-selection: hsla(204, 100%, 63%, 0.3);
25+
--sk-bg-highlight: hsl(60, 100%, 80%);
2526

2627
/* Border color — use this for all borders, except 'active' borders (e.g. current nav) which use `--sk-fg-accent` */
2728
--sk-border: hsl(0, 0%, 92%);

0 commit comments

Comments
 (0)