@adecore/editor 0.41.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controller.d.ts +4 -0
- package/dist/controller.js +30 -3
- package/dist/editor.css +6 -0
- package/dist/engine.js +52 -2
- package/dist/fake.d.ts +13 -1
- package/dist/fake.js +35 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/keymap-table.d.ts +9 -1
- package/dist/keymap-table.js +10 -0
- package/dist/keymap.js +9 -1
- package/dist/overview.d.ts +1 -1
- package/dist/todos.d.ts +23 -0
- package/dist/todos.js +86 -0
- package/dist/types.d.ts +17 -2
- package/dist/view.d.ts +6 -0
- package/dist/view.js +40 -4
- package/dist/zoom.d.ts +18 -0
- package/dist/zoom.js +41 -0
- package/package.json +2 -2
- package/src/controller.ts +43 -5
- package/src/editor.css +6 -0
- package/src/engine.ts +66 -2
- package/src/fake.ts +47 -0
- package/src/index.ts +4 -0
- package/src/keymap-table.ts +20 -1
- package/src/keymap.ts +9 -1
- package/src/overview.ts +1 -1
- package/src/todos.ts +111 -0
- package/src/types.ts +40 -3
- package/src/view.ts +48 -4
- package/src/zoom.ts +62 -0
package/src/todos.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { DEFAULT_TODO_WORDS, type DocumentModel, type TodoMatch } from '@adecore/editor-core';
|
|
2
|
+
import { overlayTokens } from './semantic.ts';
|
|
3
|
+
import type { LineToken } from './types.ts';
|
|
4
|
+
|
|
5
|
+
const TODO_COLOR = 'var(--editor-todo)';
|
|
6
|
+
const ITALIC = 1;
|
|
7
|
+
/* The marks in the scroll track read the whole document, so they wait for a pause in editing as the folds do. */
|
|
8
|
+
const TODO_DELAY_MS = 300;
|
|
9
|
+
const TODO_LIMIT = 2_000_000;
|
|
10
|
+
const CACHED_LINES = 600;
|
|
11
|
+
|
|
12
|
+
/* The text is kept with the mark, since the list is only read again after a pause and the offsets may be stale until then. */
|
|
13
|
+
export type TodoMark = TodoMatch & { readonly text: string };
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The TODO comments of a view: drawn in their own color over the grammar's on the lines being painted,
|
|
17
|
+
* and listed for the whole document a moment after the last edit, for the ticks in the scroll track.
|
|
18
|
+
*/
|
|
19
|
+
export class TodoMarks {
|
|
20
|
+
private readonly model: DocumentModel;
|
|
21
|
+
private readonly changed: () => void;
|
|
22
|
+
private words: readonly string[] = DEFAULT_TODO_WORDS;
|
|
23
|
+
/* Empty until the first `configure`, which reads the document. */
|
|
24
|
+
private language = '';
|
|
25
|
+
private revision = -1;
|
|
26
|
+
private readonly lines = new Map<number, { base: readonly LineToken[] | null; result: readonly LineToken[] | null }>();
|
|
27
|
+
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
28
|
+
private all: readonly TodoMark[] = [];
|
|
29
|
+
/* Counts every new list, so a painter can tell its ticks are stale. */
|
|
30
|
+
version = 0;
|
|
31
|
+
|
|
32
|
+
constructor(model: DocumentModel, changed: () => void) {
|
|
33
|
+
this.model = model;
|
|
34
|
+
this.changed = changed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get marks(): readonly TodoMark[] {
|
|
38
|
+
return this.all;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
configure(language: string | undefined, words: readonly string[] | undefined): void {
|
|
42
|
+
const nextLanguage = language ?? 'plaintext';
|
|
43
|
+
const nextWords = words ?? DEFAULT_TODO_WORDS;
|
|
44
|
+
|
|
45
|
+
if (nextLanguage === this.language && nextWords.join('\n') === this.words.join('\n')) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.language = nextLanguage;
|
|
50
|
+
this.words = nextWords;
|
|
51
|
+
this.revision = -1;
|
|
52
|
+
this.refresh();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* The line's tokens with its TODOs drawn over them, the same array for as long as neither changed. */
|
|
56
|
+
styled(line: number, base: readonly LineToken[] | null): readonly LineToken[] | null {
|
|
57
|
+
if (this.words.length === 0) {
|
|
58
|
+
return base;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const revision = this.model.getRevision();
|
|
62
|
+
|
|
63
|
+
if (revision !== this.revision || this.lines.size > CACHED_LINES) {
|
|
64
|
+
this.revision = revision;
|
|
65
|
+
this.lines.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cached = this.lines.get(line);
|
|
69
|
+
|
|
70
|
+
if (cached !== undefined && cached.base === base) {
|
|
71
|
+
return cached.result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const matches = this.model.findTodos(this.language, { words: this.words }, line, line);
|
|
75
|
+
let result = base;
|
|
76
|
+
|
|
77
|
+
if (matches.length > 0) {
|
|
78
|
+
const { start, end } = this.model.getLine(line);
|
|
79
|
+
const spans = matches.map((match) => ({ from: match.from - start, to: match.to - start, color: TODO_COLOR, fontStyle: ITALIC }));
|
|
80
|
+
result = overlayTokens(base ?? [{ length: end - start, color: '', fontStyle: 0 }], spans);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.lines.set(line, { base, result });
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
schedule(): void {
|
|
89
|
+
clearTimeout(this.timer);
|
|
90
|
+
this.timer = setTimeout(() => this.refresh(), TODO_DELAY_MS);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Reads the TODOs of the whole document now, which otherwise waits for a pause in editing. */
|
|
94
|
+
refresh(): void {
|
|
95
|
+
clearTimeout(this.timer);
|
|
96
|
+
const found = this.words.length === 0 || this.model.getLength() > TODO_LIMIT ? [] : this.model.findTodos(this.language, { words: this.words });
|
|
97
|
+
const next = found.map((match) => ({ ...match, text: this.model.slice(match.from, match.to) }));
|
|
98
|
+
|
|
99
|
+
if (next.length === 0 && this.all.length === 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.all = next;
|
|
104
|
+
this.version++;
|
|
105
|
+
this.changed();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
dispose(): void {
|
|
109
|
+
clearTimeout(this.timer);
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { EditorSearchClientOptions } from './search-client.ts';
|
|
2
2
|
import type { Keymap } from './keymap-table.ts';
|
|
3
|
-
import type { EditorCommand, FoldRole } from '@adecore/editor-core';
|
|
3
|
+
import type { EditorCommand, FoldRole, WhitespaceCleanupOptions } from '@adecore/editor-core';
|
|
4
4
|
|
|
5
5
|
export type { EditorCommand, FoldRole };
|
|
6
6
|
|
|
7
|
+
/* What a cleanup before a save does; everything is off unless set, except that the lines with a caret keep their trailing whitespace. */
|
|
8
|
+
export type EditorWhitespaceCleanup = WhitespaceCleanupOptions;
|
|
9
|
+
|
|
7
10
|
export type EditorFoldOutline = 'off' | 'hover' | 'always';
|
|
8
11
|
|
|
9
|
-
/* The commands that fold, which the view answers since it knows where the ranges are. */
|
|
12
|
+
/* The commands that fold or change how the text is drawn, which the view answers since it knows where the ranges are. */
|
|
10
13
|
export type EditorViewCommand =
|
|
11
14
|
| 'collapseRegion'
|
|
12
15
|
| 'expandRegion'
|
|
@@ -22,7 +25,20 @@ export type EditorViewCommand =
|
|
|
22
25
|
| 'expandAllToLevel3'
|
|
23
26
|
| 'expandAllToLevel4'
|
|
24
27
|
| 'expandAllToLevel5'
|
|
25
|
-
| 'toggleColumnMode'
|
|
28
|
+
| 'toggleColumnMode'
|
|
29
|
+
| 'increaseFontSize'
|
|
30
|
+
| 'decreaseFontSize'
|
|
31
|
+
| 'resetFontSize';
|
|
32
|
+
|
|
33
|
+
/* How far a person may zoom the text, and whether the wheel does. */
|
|
34
|
+
export interface EditorZoom {
|
|
35
|
+
/* In pixels; 12 by default, and never less. */
|
|
36
|
+
readonly min?: number;
|
|
37
|
+
/* In pixels; 90 by default. */
|
|
38
|
+
readonly max?: number;
|
|
39
|
+
/* Ctrl with the wheel (Cmd on macOS), or a pinch, zooms; on unless false. */
|
|
40
|
+
readonly wheel?: boolean;
|
|
41
|
+
}
|
|
26
42
|
|
|
27
43
|
/* Everything `runCommand` runs. */
|
|
28
44
|
export type EditorRunCommand = EditorCommand | EditorViewCommand;
|
|
@@ -87,6 +103,11 @@ export interface EditorOptions {
|
|
|
87
103
|
readonly column?: number;
|
|
88
104
|
/* In pixels, where the view opens; without it the cursor's line is brought into view. */
|
|
89
105
|
readonly scrollTop?: number;
|
|
106
|
+
/* The words that mark a TODO comment, matched as whole words in any case; `DEFAULT_TODO_WORDS` without them, and none drawn with an empty list. */
|
|
107
|
+
readonly todoWords?: readonly string[];
|
|
108
|
+
/* In pixels, a size a person zoomed to that the host kept; without it the code has the page's `--code-font-size`. */
|
|
109
|
+
readonly fontSize?: number;
|
|
110
|
+
readonly zoom?: EditorZoom;
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
/* The few sentences the editor says on its own. */
|
|
@@ -623,6 +644,8 @@ export interface Editor {
|
|
|
623
644
|
/* The column to draw a line at; null takes it away. */
|
|
624
645
|
setRightMargin(column: number | null): void;
|
|
625
646
|
setFoldOutline(outline: EditorFoldOutline): void;
|
|
647
|
+
/* The words that mark a TODO comment; an empty list draws none. Optional so an editor from before it still fits. */
|
|
648
|
+
setTodoWords?(words: readonly string[]): void;
|
|
626
649
|
setLabel(label: string): void;
|
|
627
650
|
/* What a language server knows about the folds: the bodies of symbols and the ranges it folds. They follow their text through edits until set again; null forgets them. */
|
|
628
651
|
setFoldHints(hints: EditorFoldHints | null): void;
|
|
@@ -765,9 +788,23 @@ export interface Editor {
|
|
|
765
788
|
trackRange(range: EditorRange): EditorTrackedRange;
|
|
766
789
|
/* Replaces ranges of the current text at once, as one step of the undo history. `mapSelections` places each selection in the new text, keeping its direction, and redo restores it. `mergeUndo` joins the step before it when the text is exactly as that step left it, so one undo takes both back. */
|
|
767
790
|
applyEdits(edits: readonly EditorContentChange[], options?: EditorApplyEditsOptions): boolean;
|
|
791
|
+
/*
|
|
792
|
+
* Call right before writing the file: cleans up the whitespace as the options say, as one undo step, and
|
|
793
|
+
* marks the text as saved, so `modified` counts from here next time. Call it on every save, with all off too.
|
|
794
|
+
* False when the text did not change. Optional so an editor from before it still fits.
|
|
795
|
+
*/
|
|
796
|
+
cleanUpWhitespace?(options: EditorWhitespaceCleanup): boolean;
|
|
797
|
+
/* Marks the text as saved without a cleanup, such as after a reload from disk. Optional so an editor from before it still fits. */
|
|
798
|
+
markSaved?(): void;
|
|
768
799
|
setTheme(theme: EditorTheme): void;
|
|
769
800
|
/* Reads the code face off the page again, after the page changed its size, family or ligatures. */
|
|
770
801
|
refreshFont(): void;
|
|
802
|
+
/* Draws the code at a size of its own, kept inside the zoom range in whole pixels; null goes back to the page's size. Optional so an editor from before it still fits. */
|
|
803
|
+
setFontSize?(size: number | null): void;
|
|
804
|
+
/* The size the code is drawn at, in pixels. Optional so an editor from before it still fits. */
|
|
805
|
+
getFontSize?(): number;
|
|
806
|
+
/* A person zoomed: the new size in pixels, or null when they went back to the page's size. Not told of `setFontSize`. Optional so an editor from before it still fits. */
|
|
807
|
+
onFontSizeChange?(listener: (size: number | null) => void): () => void;
|
|
771
808
|
setReadOnly(readOnly: boolean, reason?: string): void;
|
|
772
809
|
/* Whether the editor refuses edits. Optional so an editor from before it still fits. */
|
|
773
810
|
isReadOnly?(): boolean;
|
package/src/view.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type VerticalWindow
|
|
28
28
|
} from './layout.ts';
|
|
29
29
|
import { createMetrics, type EditorFont, readEditorFont } from './metrics.ts';
|
|
30
|
+
import { zoomedFont } from './zoom.ts';
|
|
30
31
|
import { type AttributedLines, AttributionRuns, colorValue, tintValue } from './attribution.ts';
|
|
31
32
|
import { renderCodeBlock } from './code-block.ts';
|
|
32
33
|
import { emit, subscribe } from './listeners.ts';
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
type WrapSign
|
|
51
52
|
} from './paint.ts';
|
|
52
53
|
import { overlayTokens, type SemanticSpan } from './semantic.ts';
|
|
54
|
+
import { TodoMarks } from './todos.ts';
|
|
53
55
|
import { TokenCache } from './tokens.ts';
|
|
54
56
|
import type {
|
|
55
57
|
EditorBlock,
|
|
@@ -118,6 +120,8 @@ export interface ViewSettings {
|
|
|
118
120
|
folding?: boolean;
|
|
119
121
|
/* The headers of the blocks scrolled out of sight stay pinned on top; on unless false. */
|
|
120
122
|
stickyScroll?: boolean;
|
|
123
|
+
/* The words that mark a TODO comment; the default ones without them, none drawn with an empty list. */
|
|
124
|
+
todoWords?: readonly string[];
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
/* The lines past the visible ones that are colored ahead of the scroll, and how long one slice of that work may take. */
|
|
@@ -201,6 +205,7 @@ export class EditorView {
|
|
|
201
205
|
readonly layout: EditorLayout;
|
|
202
206
|
readonly find: FindController;
|
|
203
207
|
readonly tokens: TokenCache;
|
|
208
|
+
private readonly todos: TodoMarks;
|
|
204
209
|
private readonly content: HTMLElement;
|
|
205
210
|
private readonly gutter: HTMLElement;
|
|
206
211
|
private readonly gutterLines: HTMLElement;
|
|
@@ -275,6 +280,8 @@ export class EditorView {
|
|
|
275
280
|
private caretTimer: ReturnType<typeof setTimeout> | undefined;
|
|
276
281
|
private caretKey = '';
|
|
277
282
|
private renderedScroll = { top: -1, left: -1 };
|
|
283
|
+
/* A size the code is zoomed to, over the page's; null draws it at the page's. */
|
|
284
|
+
private fontSize: number | null = null;
|
|
278
285
|
private font: EditorFont;
|
|
279
286
|
private layoutVersion = 0;
|
|
280
287
|
private revision: number;
|
|
@@ -424,6 +431,7 @@ export class EditorView {
|
|
|
424
431
|
});
|
|
425
432
|
|
|
426
433
|
this.tokens = new TokenCache({ getLineCount: () => model.getLineCount(), getLineText: (line) => model.getLine(line).text });
|
|
434
|
+
this.todos = new TodoMarks(model, () => this.requestRender());
|
|
427
435
|
const ResizeObserverClass = document.defaultView?.ResizeObserver;
|
|
428
436
|
|
|
429
437
|
this.blockObserver = ResizeObserverClass
|
|
@@ -459,6 +467,7 @@ export class EditorView {
|
|
|
459
467
|
this.root.dataset.readonly = String(this.settings.readOnly);
|
|
460
468
|
this.root.dataset.wrap = String(this.settings.wrap);
|
|
461
469
|
this.root.dataset.foldOutline = this.settings.foldOutline;
|
|
470
|
+
this.todos.configure(this.settings.language, this.settings.todoWords);
|
|
462
471
|
this.layoutVersion++;
|
|
463
472
|
this.layout.configure({ metrics: createMetrics(this.font, this.settings.tabSize, this.document) });
|
|
464
473
|
this.render();
|
|
@@ -470,8 +479,30 @@ export class EditorView {
|
|
|
470
479
|
}
|
|
471
480
|
|
|
472
481
|
refreshFont(): void {
|
|
473
|
-
|
|
474
|
-
|
|
482
|
+
const style = this.root.style;
|
|
483
|
+
style.removeProperty('--code-font-size');
|
|
484
|
+
style.removeProperty('--code-line-height');
|
|
485
|
+
const base = readEditorFont(this.root);
|
|
486
|
+
this.font = this.fontSize === null ? base : zoomedFont(base, this.fontSize);
|
|
487
|
+
|
|
488
|
+
// On the element itself, so everything the editor draws at the code's size follows, and the page's size stays the base.
|
|
489
|
+
if (this.fontSize !== null) {
|
|
490
|
+
style.setProperty('--code-font-size', `${this.font.size}px`);
|
|
491
|
+
style.setProperty('--code-line-height', `${this.font.lineHeight}px`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
this.anchorScroll(() => this.applySettings());
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Draws the code at a size of its own in whole pixels; null goes back to the page's. */
|
|
498
|
+
setFontSize(size: number | null): void {
|
|
499
|
+
this.fontSize = size;
|
|
500
|
+
this.refreshFont();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/* The size the code is drawn at, in pixels. */
|
|
504
|
+
getFontSize(): number {
|
|
505
|
+
return this.font.size;
|
|
475
506
|
}
|
|
476
507
|
|
|
477
508
|
setTokenizer(tokenizer: LineTokenizer | null): void {
|
|
@@ -591,8 +622,13 @@ export class EditorView {
|
|
|
591
622
|
this.render();
|
|
592
623
|
}
|
|
593
624
|
|
|
594
|
-
/* The
|
|
625
|
+
/* The colors of a line with its TODOs drawn over them, the same object for as long as nothing under them changed. */
|
|
595
626
|
private styledTokensOf(line: number): readonly LineToken[] | null {
|
|
627
|
+
return this.todos.styled(line, this.semanticTokensOf(line));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/* The grammar's colors of a line with the servers' laid over them, the same object for as long as neither changed. */
|
|
631
|
+
private semanticTokensOf(line: number): readonly LineToken[] | null {
|
|
596
632
|
const base = this.lineTokenProvider === null ? this.tokens.tokensOf(line) : (this.lineTokenProvider(line) ?? null);
|
|
597
633
|
|
|
598
634
|
if (base === null || this.semantic.length === 0 || this.scopeColors === null) {
|
|
@@ -841,6 +877,7 @@ export class EditorView {
|
|
|
841
877
|
this.find.refresh();
|
|
842
878
|
this.announceFind();
|
|
843
879
|
this.scheduleFolds();
|
|
880
|
+
this.todos.schedule();
|
|
844
881
|
this.colorAhead(true);
|
|
845
882
|
}
|
|
846
883
|
|
|
@@ -2963,7 +3000,7 @@ export class EditorView {
|
|
|
2963
3000
|
const matches = this.find.matches;
|
|
2964
3001
|
const current = this.find.current;
|
|
2965
3002
|
const alike = this.selectionOccurrences();
|
|
2966
|
-
const key = `${this.revision}|${this.changeVersion}|${this.markerVersion}|${this.layout.height}|${trackHeight}|${matches.length}|${current}|${matches[0]?.from}|${matches.at(-1)?.from}|${this.selectionOccurrenceCache.key}`;
|
|
3003
|
+
const key = `${this.revision}|${this.changeVersion}|${this.markerVersion}|${this.todos.version}|${this.layout.height}|${trackHeight}|${matches.length}|${current}|${matches[0]?.from}|${matches.at(-1)?.from}|${this.selectionOccurrenceCache.key}`;
|
|
2967
3004
|
|
|
2968
3005
|
if (key === this.overviewKey) {
|
|
2969
3006
|
return;
|
|
@@ -2999,6 +3036,12 @@ export class EditorView {
|
|
|
2999
3036
|
}
|
|
3000
3037
|
}
|
|
3001
3038
|
|
|
3039
|
+
for (const todo of this.todos.marks) {
|
|
3040
|
+
if (todo.line < this.model.getLineCount()) {
|
|
3041
|
+
spans.push({ kind: 'todo', top: rowTop(todo.line), bottom: rowBottom(todo.line), offset: todo.from, title: todo.text });
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3002
3045
|
for (const range of alike) {
|
|
3003
3046
|
const line = this.model.positionAt(range.from).line;
|
|
3004
3047
|
spans.push({ kind: 'occurrence', top: rowTop(line), bottom: rowBottom(line) });
|
|
@@ -3224,6 +3267,7 @@ export class EditorView {
|
|
|
3224
3267
|
this.blockObserver?.disconnect();
|
|
3225
3268
|
clearTimeout(this.bracketTimer);
|
|
3226
3269
|
clearTimeout(this.foldTimer);
|
|
3270
|
+
this.todos.dispose();
|
|
3227
3271
|
clearTimeout(this.colorTimer);
|
|
3228
3272
|
clearTimeout(this.noticeTimer);
|
|
3229
3273
|
clearTimeout(this.liveTimer);
|
package/src/zoom.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { EditorFont } from './metrics.ts';
|
|
2
|
+
import type { EditorZoom } from './types.ts';
|
|
3
|
+
|
|
4
|
+
/* Nothing a person reads is smaller. */
|
|
5
|
+
export const MIN_FONT_SIZE = 12;
|
|
6
|
+
export const MAX_FONT_SIZE = 90;
|
|
7
|
+
/* A trackpad or a pinch sends the wheel in small steps; this many pixels of them are one step of the size. */
|
|
8
|
+
const PIXELS_PER_STEP = 20;
|
|
9
|
+
|
|
10
|
+
export interface FontSizeRange {
|
|
11
|
+
readonly min: number;
|
|
12
|
+
readonly max: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function fontSizeRange(zoom: EditorZoom | undefined): FontSizeRange {
|
|
16
|
+
const min = Math.max(MIN_FONT_SIZE, Math.round(zoom?.min ?? MIN_FONT_SIZE));
|
|
17
|
+
|
|
18
|
+
return { min, max: Math.max(min, Math.round(zoom?.max ?? MAX_FONT_SIZE)) };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* In whole pixels inside the range. */
|
|
22
|
+
export function clampFontSize(size: number, range: FontSizeRange): number {
|
|
23
|
+
return Math.min(range.max, Math.max(range.min, Math.round(size)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* The face at another size, with the line as much taller as the page's line is to its text, in whole pixels. */
|
|
27
|
+
export function zoomedFont(base: EditorFont, size: number): EditorFont {
|
|
28
|
+
return { family: base.family, size, lineHeight: Math.round((size * base.lineHeight) / base.size) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Turns the wheel with the zoom modifier, or a pinch, into steps of one pixel: up and apart make the text larger. */
|
|
32
|
+
export class WheelZoom {
|
|
33
|
+
private pending = 0;
|
|
34
|
+
|
|
35
|
+
/* -1, 0 or 1. A mouse notch is one step however far the platform says it went. */
|
|
36
|
+
step(event: { deltaY: number; deltaMode: number }): number {
|
|
37
|
+
if (event.deltaY === 0) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (event.deltaMode !== 0 || Math.abs(event.deltaY) >= PIXELS_PER_STEP) {
|
|
42
|
+
this.pending = 0;
|
|
43
|
+
|
|
44
|
+
return event.deltaY < 0 ? 1 : -1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Math.sign(this.pending) !== 0 && Math.sign(this.pending) !== Math.sign(event.deltaY)) {
|
|
48
|
+
this.pending = 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.pending += event.deltaY;
|
|
52
|
+
|
|
53
|
+
if (Math.abs(this.pending) < PIXELS_PER_STEP) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const direction = this.pending < 0 ? 1 : -1;
|
|
58
|
+
this.pending = 0;
|
|
59
|
+
|
|
60
|
+
return direction;
|
|
61
|
+
}
|
|
62
|
+
}
|