aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-11-01 12:05:21 -0400
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-11-05 19:41:33 +0100
commitffe5b08149681f51625bdac43c7ab41ccf5f9cc3 (patch)
treef19137f20c73613ed8d4e63a725e4a1d20a00ec2 /subprojects/frontend/src/editor/scrollbarViewPlugin.ts
parentfeat(frontend): overlay scrollbars for editor (diff)
downloadrefinery-ffe5b08149681f51625bdac43c7ab41ccf5f9cc3.tar.gz
refinery-ffe5b08149681f51625bdac43c7ab41ccf5f9cc3.tar.zst
refinery-ffe5b08149681f51625bdac43c7ab41ccf5f9cc3.zip
feat(frontend): scrollbar annotations
Diffstat (limited to 'subprojects/frontend/src/editor/scrollbarViewPlugin.ts')
-rw-r--r--subprojects/frontend/src/editor/scrollbarViewPlugin.ts106
1 files changed, 103 insertions, 3 deletions
diff --git a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
index 2882f02e..c95e581d 100644
--- a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
+++ b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts
@@ -2,6 +2,7 @@ import { type PluginValue, ViewPlugin } from '@codemirror/view';
2import { reaction } from 'mobx'; 2import { reaction } from 'mobx';
3 3
4import type EditorStore from './EditorStore'; 4import type EditorStore from './EditorStore';
5import findOccurrences from './findOccurrences';
5 6
6export const HOLDER_CLASS = 'cm-scroller-holder'; 7export const HOLDER_CLASS = 'cm-scroller-holder';
7export const THUMB_CLASS = 'cm-scroller-thumb'; 8export const THUMB_CLASS = 'cm-scroller-thumb';
@@ -10,8 +11,13 @@ export const THUMB_X_CLASS = 'cm-scroller-thumb-x';
10export const THUMB_ACTIVE_CLASS = 'active'; 11export const THUMB_ACTIVE_CLASS = 'active';
11export const GUTTER_DECORATION_CLASS = 'cm-scroller-gutter-decoration'; 12export const GUTTER_DECORATION_CLASS = 'cm-scroller-gutter-decoration';
12export const TOP_DECORATION_CLASS = 'cm-scroller-top-decoration'; 13export const TOP_DECORATION_CLASS = 'cm-scroller-top-decoration';
14export const ANNOTATION_SELECTION_CLASS = 'cm-scroller-selection';
15export const ANNOTATION_DIAGNOSTIC_CLASS = 'cm-scroller-diagnostic';
16export const ANNOTATION_OCCURRENCE_CLASS = 'cm-scroller-occurrence';
13export const SHADOW_WIDTH = 10; 17export const SHADOW_WIDTH = 10;
14export const SCROLLBAR_WIDTH = 12; 18export const SCROLLBAR_WIDTH = 12;
19export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2;
20export const MIN_ANNOTATION_HEIGHT = 1;
15 21
16export default function scrollbarViewPlugin( 22export default function scrollbarViewPlugin(
17 editorStore: EditorStore, 23 editorStore: EditorStore,
@@ -94,6 +100,84 @@ export default function scrollbarViewPlugin(
94 let gutters: Element | undefined; 100 let gutters: Element | undefined;
95 101
96 let requested = false; 102 let requested = false;
103 let rebuildRequested = false;
104
105 const annotations: HTMLDivElement[] = [];
106
107 function rebuildAnnotations(trackYHeight: number) {
108 const annotationOverlayHeight = Math.min(
109 view.contentHeight,
110 trackYHeight,
111 );
112 const lineHeight = annotationOverlayHeight / editorStore.state.doc.lines;
113
114 let i = 0;
115
116 function getOrCreateAnnotation(
117 from: number,
118 to?: number,
119 ): HTMLDivElement {
120 const startLine = editorStore.state.doc.lineAt(from).number;
121 const endLine =
122 to === undefined
123 ? startLine
124 : editorStore.state.doc.lineAt(to).number;
125 const top = (startLine - 1) * lineHeight;
126 const height = Math.max(
127 MIN_ANNOTATION_HEIGHT,
128 Math.max(1, endLine - startLine) * lineHeight,
129 );
130
131 let annotation: HTMLDivElement;
132 if (i < annotations.length) {
133 annotation = annotations[i];
134 } else {
135 annotation = ownerDocument.createElement('div');
136 annotations.push(annotation);
137 holder.appendChild(annotation);
138 }
139 i += 1;
140
141 annotation.style.top = `${top}px`;
142 annotation.style.height = `${height}px`;
143
144 return annotation;
145 }
146
147 editorStore.state.selection.ranges.forEach(({ head }) => {
148 const selectionAnnotation = getOrCreateAnnotation(head);
149 selectionAnnotation.className = ANNOTATION_SELECTION_CLASS;
150 selectionAnnotation.style.width = `${SCROLLBAR_WIDTH}px`;
151 });
152
153 const diagnosticsIter = editorStore.diagnostics.iter();
154 while (diagnosticsIter.value !== null) {
155 const diagnosticAnnotation = getOrCreateAnnotation(
156 diagnosticsIter.from,
157 diagnosticsIter.to,
158 );
159 diagnosticAnnotation.className = `${ANNOTATION_DIAGNOSTIC_CLASS} ${ANNOTATION_DIAGNOSTIC_CLASS}-${diagnosticsIter.value.severity}`;
160 diagnosticAnnotation.style.width = `${ANNOTATION_WIDTH}px`;
161 diagnosticsIter.next();
162 }
163
164 const occurrences = editorStore.state.field(findOccurrences);
165 const occurrencesIter = occurrences.iter();
166 while (occurrencesIter.value !== null) {
167 const occurrenceAnnotation = getOrCreateAnnotation(
168 occurrencesIter.from,
169 occurrencesIter.to,
170 );
171 occurrenceAnnotation.className = ANNOTATION_OCCURRENCE_CLASS;
172 occurrenceAnnotation.style.width = `${ANNOTATION_WIDTH}px`;
173 occurrenceAnnotation.style.right = `${ANNOTATION_WIDTH}px`;
174 occurrencesIter.next();
175 }
176
177 annotations
178 .splice(i)
179 .forEach((staleAnnotation) => holder.removeChild(staleAnnotation));
180 }
97 181
98 function update() { 182 function update() {
99 requested = false; 183 requested = false;
@@ -148,6 +232,11 @@ export default function scrollbarViewPlugin(
148 0, 232 0,
149 Math.min(scrollTop, SHADOW_WIDTH), 233 Math.min(scrollTop, SHADOW_WIDTH),
150 )}px`; 234 )}px`;
235
236 if (rebuildRequested) {
237 rebuildAnnotations(trackYHeight);
238 rebuildRequested = false;
239 }
151 } 240 }
152 241
153 function requestUpdate() { 242 function requestUpdate() {
@@ -157,16 +246,27 @@ export default function scrollbarViewPlugin(
157 } 246 }
158 } 247 }
159 248
160 observer = new ResizeObserver(requestUpdate); 249 function requestRebuild() {
250 requestUpdate();
251 rebuildRequested = true;
252 }
253
254 observer = new ResizeObserver(requestRebuild);
161 observer.observe(scrollDOM); 255 observer.observe(scrollDOM);
162 256
163 scrollDOM.addEventListener('scroll', requestUpdate); 257 scrollDOM.addEventListener('scroll', requestUpdate);
164 258
165 requestUpdate(); 259 requestRebuild();
260
261 const disposeRebuildReaction = reaction(
262 () => editorStore.diagnostics,
263 requestRebuild,
264 );
166 265
167 return { 266 return {
168 update: requestUpdate, 267 update: requestRebuild,
169 destroy() { 268 destroy() {
269 disposeRebuildReaction();
170 disposePanelReaction(); 270 disposePanelReaction();
171 observer?.disconnect(); 271 observer?.disconnect();
172 scrollDOM.removeEventListener('scroll', requestUpdate); 272 scrollDOM.removeEventListener('scroll', requestUpdate);