@adecore/editor 0.0.1 → 0.17.0-beta.1

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.
Files changed (107) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +82 -0
  3. package/dist/attribution.d.ts +28 -0
  4. package/dist/attribution.js +55 -0
  5. package/dist/code-block.d.ts +2 -0
  6. package/dist/code-block.js +89 -0
  7. package/dist/column-selection.d.ts +9 -0
  8. package/dist/column-selection.js +52 -0
  9. package/dist/controller.d.ts +64 -0
  10. package/dist/controller.js +772 -0
  11. package/dist/editor.css +813 -0
  12. package/dist/engine.d.ts +2 -0
  13. package/dist/engine.js +604 -0
  14. package/dist/fake.d.ts +190 -0
  15. package/dist/fake.js +658 -0
  16. package/dist/find.d.ts +30 -0
  17. package/dist/find.js +108 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.js +4 -0
  20. package/dist/input.d.ts +9 -0
  21. package/dist/input.js +57 -0
  22. package/dist/keymap-table.d.ts +102 -0
  23. package/dist/keymap-table.js +130 -0
  24. package/dist/keymap.d.ts +36 -0
  25. package/dist/keymap.js +163 -0
  26. package/dist/layout.d.ts +167 -0
  27. package/dist/layout.js +583 -0
  28. package/dist/lens.d.ts +3 -0
  29. package/dist/lens.js +29 -0
  30. package/dist/listeners.d.ts +3 -0
  31. package/dist/listeners.js +12 -0
  32. package/dist/metrics.d.ts +8 -0
  33. package/dist/metrics.js +54 -0
  34. package/dist/modifier-gesture.d.ts +24 -0
  35. package/dist/modifier-gesture.js +98 -0
  36. package/dist/native-input.d.ts +26 -0
  37. package/dist/native-input.js +147 -0
  38. package/dist/offsets.d.ts +2 -0
  39. package/dist/offsets.js +14 -0
  40. package/dist/outline.d.ts +55 -0
  41. package/dist/outline.js +181 -0
  42. package/dist/overview.d.ts +17 -0
  43. package/dist/overview.js +55 -0
  44. package/dist/paint.d.ts +86 -0
  45. package/dist/paint.js +481 -0
  46. package/dist/php-html.d.ts +9 -0
  47. package/dist/php-html.js +36 -0
  48. package/dist/pointer.d.ts +49 -0
  49. package/dist/pointer.js +243 -0
  50. package/dist/scroll-animation.d.ts +3 -0
  51. package/dist/scroll-animation.js +45 -0
  52. package/dist/scroll.d.ts +28 -0
  53. package/dist/scroll.js +76 -0
  54. package/dist/semantic.d.ts +8 -0
  55. package/dist/semantic.js +47 -0
  56. package/dist/shiki.d.ts +7 -0
  57. package/dist/shiki.js +106 -0
  58. package/dist/smart-keys.d.ts +3 -0
  59. package/dist/smart-keys.js +14 -0
  60. package/dist/testing.d.ts +34 -0
  61. package/dist/testing.js +117 -0
  62. package/dist/theme-scopes.d.ts +9 -0
  63. package/dist/theme-scopes.js +61 -0
  64. package/dist/tokens.d.ts +28 -0
  65. package/dist/tokens.js +152 -0
  66. package/dist/tracked-range.d.ts +5 -0
  67. package/dist/tracked-range.js +20 -0
  68. package/dist/types.d.ts +342 -0
  69. package/dist/types.js +1 -0
  70. package/dist/view.d.ts +382 -0
  71. package/dist/view.js +2134 -0
  72. package/package.json +63 -3
  73. package/src/attribution.ts +85 -0
  74. package/src/code-block.ts +100 -0
  75. package/src/column-selection.ts +63 -0
  76. package/src/controller.ts +838 -0
  77. package/src/editor.css +813 -0
  78. package/src/engine.ts +757 -0
  79. package/src/fake.ts +831 -0
  80. package/src/find.ts +128 -0
  81. package/src/index.ts +69 -0
  82. package/src/input.ts +63 -0
  83. package/src/keymap-table.ts +163 -0
  84. package/src/keymap.ts +204 -0
  85. package/src/layout.ts +754 -0
  86. package/src/lens.ts +32 -0
  87. package/src/listeners.ts +15 -0
  88. package/src/metrics.ts +65 -0
  89. package/src/modifier-gesture.ts +115 -0
  90. package/src/native-input.ts +179 -0
  91. package/src/offsets.ts +16 -0
  92. package/src/outline.ts +248 -0
  93. package/src/overview.ts +80 -0
  94. package/src/paint.ts +608 -0
  95. package/src/php-html.ts +43 -0
  96. package/src/pointer.ts +326 -0
  97. package/src/scroll-animation.ts +48 -0
  98. package/src/scroll.ts +102 -0
  99. package/src/semantic.ts +54 -0
  100. package/src/shiki.ts +118 -0
  101. package/src/smart-keys.ts +17 -0
  102. package/src/testing.ts +157 -0
  103. package/src/theme-scopes.ts +75 -0
  104. package/src/tokens.ts +175 -0
  105. package/src/tracked-range.ts +22 -0
  106. package/src/types.ts +656 -0
  107. package/src/view.ts +2436 -0
package/LICENSE ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Bas Milius
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @adecore/editor
2
+
3
+ Read the [browser editor handbook](https://adecore.dev/editor/handbook/) for the complete setup, concepts, host integration and testing guides.
4
+
5
+ The DOM view of the smart editor. It draws a `DocumentModel` from `@adecore/editor-core` with its own layout and implements the `EditorEngine` contract. Language features live in `@adecore/editor-react`; the engine itself needs no language service.
6
+
7
+ ```ts
8
+ const engine = createSmartEditorEngine({
9
+ tokenizer: shikiTokenizers(() => highlighter),
10
+ handBack: shellShortcuts,
11
+ apple
12
+ });
13
+ const editor = engine.mount(element, { text, language: 'typescript', theme: 'github-dark' });
14
+ ```
15
+
16
+ Import `@adecore/ui/theme.css`, then `@adecore/editor/editor.css`. The element needs a positioned box sized by its parent. Editor, find, change and attribution colors have defaults on Adecore theme tokens. The code face uses `--font-mono`, `--code-font-size` and `--code-line-height`; the latter two default to `--text-code` and `--text-code--line-height`. Override them after the editor CSS, and call `refreshFont()` when they change.
17
+
18
+ ## How it is put together
19
+
20
+ - `layout.ts`: the rows of the document, free of the DOM. A row is a line, a widget or a fold's first line. It owns wrapping into visual lines, tab stops, inlays, folded lines, caret and selection boxes, hit testing and moving by visual line. Only the rows on screen are measured; the others stand on an estimate that the first draw corrects.
21
+ - `view.ts`: the DOM, the scroll (native: the gutter and the pinned headers stick to the corner of the one scroll container, and the scroll event draws the rows, a viewport of them on each side, in the frame it shows in), the folds and what is drawn behind and above the text. `paint.ts` holds the drawing of rows, the gutter, the selection and the carets. Rows are absolutely placed runs of text, so the layout decides where each character is. A draw reads the viewport's scroll position and size once at its start, since a read after the first write of a frame makes the browser lay the page out again, and a host setter whose value did not change draws nothing. The horizontal scroll extent is the widest line measured since the text last changed, inlays included, plus three columns as on the platform; a host row follows the scroll and clips what it draws past its width, since an overflow there would grow the extent with every scroll and carry the sticky gutter out of view.
22
+ - `controller.ts`: what a person does. Keys come from one table (`keymap-table.ts`, which the client prints in its menus as well) and are mapped in `keymap.ts` to the model's commands, typing goes through `DocumentModel.typeText`, a paste through `DocumentModel.paste`, the mouse through `pointer.ts`. A copy or cut of a bare caret remembers its text for every editor of the page, and pasting that same text again, in any of them, lands as whole lines. Adding a caret above or below is the double tap of a modifier and an arrow (`modifier-gesture.ts`). The smart keys (`EditorSmartKeys`: pairing, surrounding, tabbing out, smart Enter, indent on paste, the smart semicolon and the camel humps) are options of the editor, set at mounting and changed with `setSmartKeys`; all are on except the camel humps. The textarea (`native-input.ts`) is only the sink for the platform's typing, composition and paste, and carries the text around the caret for assistive technology.
23
+ - `tokens.ts` and `shiki.ts`: coloring. A grammar needs the state the previous line ended in, so lines are colored from a frontier down, a slice at a time, on the screen plus a margin. An edit moves the frontier to the changed line and stops as soon as a line ends in the state it had before. The tokenizer is injected (`TokenizerSource`); `shikiTokenizers` adapts the viewer's Shiki highlighter. It loads the languages a grammar embeds only on demand (a style block's `lang="scss"`, a Markdown fence) when the document names them, since Shiki rebuilds the host grammar when one loads later, and a tokenizer says so with `stale` so the colors start over from the top. A language added to a document after it opened (a new fence) is colored once the file opens again. `php-html.ts` is the grammar for a PHP file as a document (HTML with PHP between its tags), which Shiki does not bundle; PHP inside an HTML attribute is not colored.
24
+ - `outline.ts`: the blocks of the document with a header line, read from braces and indentation until the host hands over better ones with `setBlocks` (a language server's symbols). Sticky scroll pins the headers of the blocks scrolled out of sight, and `onScope` reports the named blocks around the caret for a breadcrumb.
25
+ - `overview.ts`: the ticks in the scroll track for the host's change marks (`setChangeMarks`, also drawn in the gutter) and the find matches.
26
+ - `find.ts`: the matches of the host's find bar. They are marked in the editor and never touch the selection until `endFind`.
27
+ - `semantic.ts` and `theme-scopes.ts`: what a language server classified, drawn over the grammar's colors. The host hands scopes (`setSemanticTokens`), and the theme's own rules decide the color (`scopeColors`, `shikiScopeColors`), so a class is the theme's class color. A scope the theme says nothing about keeps what the grammar made.
28
+ - `scroll.ts` and `scroll-animation.ts`: where a scroll goes (the platform's scrolling model: margins, and a jump a third from the top) and how it gets there, over up to a tenth of a second and never for a line or less.
29
+ - `keymap-table.ts`: the keys of every editor and language command for macOS and the other platforms, which the client prints in its menus too (`@adecore/editor/keymap`).
30
+ - `engine.ts`: the `Editor` contract over a model and a view.
31
+
32
+ ## What a language feature gets
33
+
34
+ The editor knows no language server. It reports every change of the text as LSP content changes (`onTextChange`, with its source), says where the caret is (`getCaret`, `onCaret`) and where a character is on the screen (`rectAt`, in the page's pixels so a popup is right at any canvas zoom), and takes what a host draws or does: `setMarkers` (squiggles, faded and struck text, a lane in the scroll track), `setInlayHints`, `setSemanticTokens`, `setHighlights`, `setBlocks`, `setWidgets` (rows of the host's own DOM next to lines, drawn in the layout's block rows; `placement: 'above'` puts one over its line, and each owner has its own set, see below), `setCodeVision` (a quiet row above a declaration, see below), `setGhostText` (a suggestion after the caret, see below), `setGutterAction` (one button in the gutter, such as a lightbulb, which `onGutterAction` reports), `setGutterMarkers` (small buttons in the gutter per owner, such as the mark of a saved inline edit, which `onGutterMarker` reports by id; a marker stays on its line until its owner sets them again), `setLink` (a name underlined as a link while a modifier is held) `trackRange` (a range that follows the text through edits and reads null once an edit touched it, inside or across an end; text inserted exactly at an end does not count, so a range that was a selection still holds the same text) and `applyEdits`. A marker's `message` is what the tick of the problem says in the scroll track, where a press on the tick goes to the problem. `getSelection`, `getSelections`, `setSelections` (the last range is the primary one), `getVisibleRange` and `getIndentation` say what a feature asks about, and `setSelection` selects a range, such as the stop of a snippet. `onHover` reports the character under the pointer, `onClick` offers a press on a character first (Mod+click follows a name) and `onContextMenu` asks the host for its menu, which the editor does not draw, `onKeyDown` lets a host take a key before the editor does, and `onViewChange` says when the screen position of a character moved.
35
+
36
+ `setText` applies the difference as a line diff in one batch (`changedSpans`), one undo step reported as an external change, so a reload from disk or a sync between two editors on a file keeps the scroll, carets, folds, markers, inlays and code vision rows wherever the text around them stayed. A language server hears it as the one stretch from the first change to the last.
37
+
38
+ `runCommand` runs an editing command by name, which is how a menu or the palette reaches the editor; the folding commands (collapse and expand a region, all, recursively, a selection, documentation comments and "expand all to level 1 through 5") and column mode are the view's own.
39
+
40
+ A collapsed fold is one row for the line commands, as the platform has it: delete, duplicate, comment, move and a copy or cut with no selection take the whole fold (a moved fold stays collapsed on its new lines), a word move steps over it and add caret above and below skips it. The view hands the commands its rows through `lineSpan`.
41
+
42
+ Folds are kept by line (`getFolds`, and `folds` on mounting), the way scroll and caret are. A fold has a role (the file header, imports, documentation comments, regions, the body of a function, a method or a class, object and array literals, tags, attributes and so on, `FoldRole` of the core), and `foldDefaults` on mounting lists the roles that fold by themselves in a file that has no folds of its own. `setFoldHints` hands over a language server's symbols and folding ranges, which name the bodies and tags the text cannot, and the defaults fold what they name once: not a fold somebody opened or closed, not one that holds the caret, and nothing after the first edit. `foldOutline` (`off`, `hover` or `always`) says when the arrow shows in the gutter. Indent guides, whitespace and a right margin are options and setters (`guides`, `whitespace`, `rightMargin`). The find takes `inSelection`, `selectFindMatches` puts a caret on every match, `setReplacePreview` draws what a regular expression writes under the current match, and `findFromCursor` steps without a find bar.
43
+
44
+ `handBack` shortcuts are not handled by the editor and not prevented, so the page's own listeners get them. Mod+S is `onSave`.
45
+
46
+ ## Agent marks
47
+
48
+ `setAttributionMarks` draws a three pixel bar in the gutter, where the change marks are, for each run of lines (`id`, one-based `startLine` and `endLine`, `color`). The color is a custom property of the page (`--agent-1` to `--agent-6`, `AGENT_COLORS`, defined in `editor.css` for both themes, with `--agent-ink` for text on them) or any CSS color. On a line that has both, the bar stands in the place of the git change mark, which the scroll track still shows; a removal triangle between lines stays. A run follows its text through edits until the host sets the marks again, and an edit costs one mapping of two offsets per run, so thousands of runs stay smooth. `onAttributionHover` reports the id and the bar's box on the line under the pointer (a few pixels either side of the bar count), and null when it leaves.
49
+
50
+ `setRemoteCursors` draws the caret of an agent (`id`, `position`, `name`, `color`, the same colors) over the text, two pixels wide with its name in a label above it, the label's lower left corner square. Where there is no room above the caret (the first line, or under the pinned headers) the name goes under it. It follows its text through edits until set again, is not interactive, and is left out on a line a fold hides. The editor's own caret and selection never move for it.
51
+
52
+ ## Review and conflict rows
53
+
54
+ `setLineHighlights` tints lines behind the text (`startLine` and `endLine` one-based, a `color`, an optional `sign` such as `+` in the gutter beside the number, an optional `fill` that paints the rows as it stands instead of the 14% tint of the color), one box per row so a widget row between two tinted lines stays clear. It follows its text through edits like the marks do. `renderCode(container, text, options)` fills a widget row's element with lines that are not in the document, such as the lines an agent removed or the other side of a conflict, in the editor's face and token colors, with a gutter column as wide as the editor's, so the text lines up with the document's (`firstLine` for numbers, `sign`, `color` for the tint and the bar, `emphasis` for character ranges per line drawn in a stronger tint, such as the words a change replaced, and `faded` for text that is gone). Both take the same colors as the attribution marks. A widget has `placement: 'above'` for a row over its line. `setLineActions(actions, owner?)` puts the host's own DOM after the last character of a line (`id`, zero-based `line`, `render(container)` once per action), such as the buttons of a change under review. It is no row: it takes no height, follows its line through edits, is hidden where a fold hides the line, and a press on it never moves the caret. A long line with no wrap pushes it past the edge of the view.
55
+
56
+ ## Widgets per owner
57
+
58
+ `setWidgets(widgets, owner?)` replaces the rows of one owner, `default` without one, and leaves every other owner's alone, so a peek, the review rows of an agent's change and a conflict stand together. Rows next to the same line are ordered by their owners' names and then in the order each owner gave them, not by who set them last, so they never swap places. An empty list takes the owner's rows away.
59
+
60
+ ## Code vision rows
61
+
62
+ `setCodeVision` takes a row per declaration: the line, and the entries of the row (`id`, `text`, an optional `user` or `users` icon, and `activate`, which gets the box of the pressed entry in the page's pixels). The row is a block row above the line (`lens` on a `BlockWidget`, `lens.ts` for its buttons), one code line high whatever its entries are, placed at the indentation of the declaration and never measured, so it holds its height with no entries and the text does not move when they arrive. It follows its declaration through edits like every block does, is kept apart from `setWidgets` (a peek under a name never takes a row away), and is drawn again only when the words of its entries change. An entry stands a text size from the next one, is muted, and lights up as a link with an underline under the pointer; a press on it never moves the caret or takes the focus.
63
+
64
+ ## Ghost text
65
+
66
+ `setGhostText({ position, text, accessory? })` draws a suggestion after the caret that is not in the document, such as a continuation from a model, and `null` takes it away. The first line of `text` sits in the line in the code face and the editor's faint color, italic, and the caret stays in front of it. Further lines are a row under the line, in the same color and with the gutter column of the document, so the lines below move down while it is shown. `accessory` fills an element drawn after the end of the line, such as the keys that take the suggestion. The editor never accepts it: the host binds the keys (Tab, `acceptGhostWord`) and inserts the text with `applyEdits`, which is one undo step. Any change of the text takes the suggestion away, so the host sets it again for the text it was made for; moving the caret does not, and the host decides what that means.
67
+
68
+ ## Limits
69
+
70
+ - No rendered documentation blocks.
71
+ - Wrapped lines break at spaces and inside a word that does not fit, and continue at the line's indentation plus two characters. A click past the end of a visual line draws the caret at its end until the caret moves; an arrow key onto that offset draws it at the start of the next line.
72
+ - A line over 20,000 characters is not colored.
73
+ - Every edit rebuilds the row list, which is linear in the number of lines.
74
+ - No bidirectional text, and no screen reader testing beyond the textarea's own context.
75
+
76
+ ## Keymaps and package exports
77
+
78
+ `@adecore/editor/keymap` exports `KEYMAP`, `resolveKeymap`, `chordOf` and `parseChord`. Pass the same resolved keymap and `apple` flag to the engine and to `ProjectLanguage` so editor keys, language commands and menu hints agree. Application collisions belong in host overrides. AI commands have no universal bindings. `handBack` remains the host's global shortcut list.
79
+
80
+ The root exports the engine and its types. `/shiki` supplies the Shiki adapter, `/fake` supplies the DOM-free `FakeEditorEngine`, and `/testing` supplies DOM fixtures backed by LinkeDOM. `/editor.css` supplies the required editor chrome.
81
+
82
+ This unpublished package is private at version `0.0.0` and retains FSL-1.1-MIT. Source consumers enable the `source` condition in the bundler, TypeScript and Bun. Default exports use compiled JavaScript and declarations. Run `bun run build`, `bun run typecheck` and `bun run test` from this package after workspace installation. See the editor migration guide for consumer adapters and the transferred revision.
@@ -0,0 +1,28 @@
1
+ import type { DocumentChange } from '@adecore/editor-core';
2
+ export declare const AGENT_COLORS: readonly ['--agent-1', '--agent-2', '--agent-3', '--agent-4', '--agent-5', '--agent-6'];
3
+ export interface AttributedLines {
4
+ readonly id: string;
5
+ readonly color: string;
6
+ readonly sign?: string;
7
+ readonly fill?: string;
8
+ }
9
+ export declare function tintValue(color: string): string;
10
+ export declare function colorValue(color: string): string;
11
+ export declare class AttributionRuns {
12
+ private runs;
13
+ get size(): number;
14
+ set(marks: readonly {
15
+ id: string;
16
+ startLine: number;
17
+ endLine: number;
18
+ color: string;
19
+ sign?: string;
20
+ fill?: string;
21
+ }[], lineCount: number, lineBounds: (line: number) => {
22
+ start: number;
23
+ end: number;
24
+ }): void;
25
+ clear(): void;
26
+ map(changes: readonly DocumentChange[]): void;
27
+ linesIn(first: number, last: number, from: number, to: number, lineOf: (offset: number) => number): Map<number, AttributedLines>;
28
+ }
@@ -0,0 +1,55 @@
1
+ import { mapOffset } from './offsets.js';
2
+ /* The custom properties of the agent palette, defined in `editor.css` for both themes. */
3
+ export const AGENT_COLORS = ['--agent-1', '--agent-2', '--agent-3', '--agent-4', '--agent-5', '--agent-6'];
4
+ /* A color washed out to a background that text stays readable on in either theme. */
5
+ export function tintValue(color) {
6
+ return `color-mix(in srgb, ${colorValue(color)} 14%, transparent)`;
7
+ }
8
+ /* The CSS value of a mark's color: `--agent-1` is the page's custom property of that name, anything else is a color as it stands. */
9
+ export function colorValue(color) {
10
+ return color.startsWith('--') ? `var(${color})` : color;
11
+ }
12
+ /*
13
+ * The runs of lines a host attributed, anchored by offsets so they follow their text through edits. An
14
+ * edit costs one mapping of two offsets per run, and a read works out the lines of only the runs that
15
+ * reach the lines asked for.
16
+ */
17
+ export class AttributionRuns {
18
+ runs = [];
19
+ get size() {
20
+ return this.runs.length;
21
+ }
22
+ /* Marks name lines one-based and inclusive; `lineBounds` answers for the document as it is now. */
23
+ set(marks, lineCount, lineBounds) {
24
+ const clamp = (line) => Math.min(lineCount, Math.max(1, Math.trunc(line) || 1)) - 1;
25
+ this.runs = marks.map((mark) => {
26
+ const start = clamp(mark.startLine);
27
+ const end = Math.max(start, clamp(mark.endLine));
28
+ return { id: mark.id, color: mark.color, sign: mark.sign, fill: mark.fill, from: lineBounds(start).start, to: lineBounds(end).end };
29
+ });
30
+ }
31
+ clear() {
32
+ this.runs = [];
33
+ }
34
+ map(changes) {
35
+ for (const run of this.runs) {
36
+ run.from = mapOffset(run.from, changes);
37
+ run.to = mapOffset(run.to, changes);
38
+ }
39
+ }
40
+ /* The run on each line from `first` to `last`, zero-based, where `from` and `to` are the offsets they span; where runs overlap the one set last wins. */
41
+ linesIn(first, last, from, to, lineOf) {
42
+ const lines = new Map();
43
+ for (const run of this.runs) {
44
+ if (run.to < from || run.from > to) {
45
+ continue;
46
+ }
47
+ const value = { id: run.id, color: run.color, sign: run.sign, fill: run.fill };
48
+ const end = Math.min(Math.max(lineOf(run.from), lineOf(run.to)), last);
49
+ for (let line = Math.max(lineOf(run.from), first); line <= end; line++) {
50
+ lines.set(line, value);
51
+ }
52
+ }
53
+ return lines;
54
+ }
55
+ }
@@ -0,0 +1,2 @@
1
+ import type { EditorCodeBlockOptions, LineToken } from './types.ts';
2
+ export declare function renderCodeBlock(container: HTMLElement, text: string, tokens: readonly (readonly LineToken[])[] | null, tabSize: number, options: EditorCodeBlockOptions): void;
@@ -0,0 +1,89 @@
1
+ import { colorValue, tintValue } from './attribution.js';
2
+ import { styleSpan } from './paint.js';
3
+ /*
4
+ * Lines of code that are not part of the document, in the editor's own face, colors and gutter, for a
5
+ * widget row to show text that is not in the file: lines an agent removed, or the other side of a
6
+ * conflict. The gutter column is as wide as the editor's, so the text lines up with the document's.
7
+ */
8
+ export function renderCodeBlock(container, text, tokens, tabSize, options) {
9
+ const document = container.ownerDocument;
10
+ const block = document.createElement('div');
11
+ block.className = 'se-code-block';
12
+ block.style.tabSize = String(tabSize);
13
+ const lines = text.split(/\r?\n/);
14
+ lines.forEach((line, index) => {
15
+ const row = document.createElement('div');
16
+ row.className = 'se-code-row';
17
+ if (options.color !== undefined) {
18
+ row.style.background = tintValue(options.color);
19
+ }
20
+ const gutter = document.createElement('div');
21
+ gutter.className = 'se-code-gutter';
22
+ if (options.firstLine !== undefined) {
23
+ const number = document.createElement('span');
24
+ number.textContent = String(options.firstLine + index);
25
+ gutter.append(number);
26
+ }
27
+ if (options.sign) {
28
+ const sign = document.createElement('span');
29
+ sign.className = 'se-line-sign';
30
+ sign.textContent = options.sign;
31
+ if (options.color !== undefined) {
32
+ sign.style.color = colorValue(options.color);
33
+ }
34
+ gutter.append(sign);
35
+ }
36
+ if (options.color !== undefined) {
37
+ const bar = document.createElement('span');
38
+ bar.className = 'se-attribution';
39
+ bar.style.background = colorValue(options.color);
40
+ gutter.append(bar);
41
+ }
42
+ const code = document.createElement('div');
43
+ code.className = 'se-code-text';
44
+ if (options.faded) {
45
+ code.dataset.faded = 'true';
46
+ }
47
+ const emphasis = options.emphasis?.[index] ?? [];
48
+ if (emphasis.length > 0 && options.color !== undefined) {
49
+ row.style.setProperty('--se-emphasis', colorValue(options.color));
50
+ }
51
+ fill(code, line, tokens?.[index] ?? null, emphasis);
52
+ row.append(gutter, code);
53
+ block.append(row);
54
+ });
55
+ container.append(block);
56
+ }
57
+ function fill(element, line, tokens, emphasis) {
58
+ if (tokens === null && emphasis.length === 0) {
59
+ element.textContent = line;
60
+ return;
61
+ }
62
+ const spans = [];
63
+ let start = 0;
64
+ for (const token of tokens ?? []) {
65
+ const end = Math.min(line.length, start + token.length);
66
+ if (end > start) {
67
+ spans.push({ start, end, token });
68
+ }
69
+ start = end;
70
+ }
71
+ if (start < line.length) {
72
+ spans.push({ start, end: line.length, token: null });
73
+ }
74
+ for (const { start: from, end: to, token } of spans) {
75
+ // A token is cut at the edges of every emphasis that crosses it, so each piece is one color and either on or off.
76
+ const cuts = [...new Set([from, to, ...emphasis.flatMap((range) => range).filter((cut) => cut > from && cut < to)])].sort((left, right) => left - right);
77
+ for (let i = 0; i < cuts.length - 1; i++) {
78
+ const piece = element.ownerDocument.createElement('span');
79
+ piece.textContent = line.slice(cuts[i], cuts[i + 1]);
80
+ if (token !== null) {
81
+ styleSpan(piece, token);
82
+ }
83
+ if (emphasis.some(([emphasisStart, emphasisEnd]) => emphasisStart <= cuts[i] && cuts[i + 1] <= emphasisEnd)) {
84
+ piece.className = 'se-code-emphasis';
85
+ }
86
+ element.append(piece);
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,9 @@
1
+ import type { Selection } from '@adecore/editor-core';
2
+ import { type EditorLayout } from './layout.ts';
3
+ export interface ContentPoint {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export declare function cellAt(layout: EditorLayout, x: number): number;
8
+ export declare function columnSelections(layout: EditorLayout, from: ContentPoint, to: ContentPoint): Selection[];
9
+ export declare function sameCell(layout: EditorLayout, from: ContentPoint, to: ContentPoint): boolean;
@@ -0,0 +1,52 @@
1
+ import { TEXT_PADDING } from './layout.js';
2
+ /* The cell of a monospaced grid an x falls in, which stands in for the visual column since a tab is a whole number of cells. */
3
+ export function cellAt(layout, x) {
4
+ return Math.max(0, Math.round((x - TEXT_PADDING) / layout.metrics.charWidth));
5
+ }
6
+ /*
7
+ * One selection per line between two points, each spanning the same visual columns, the way a column
8
+ * (box) selection works. A line that ends before the box starts has no text in it: it gets a caret at
9
+ * its end when the box is only a column of carets, and none otherwise.
10
+ */
11
+ export function columnSelections(layout, from, to) {
12
+ const { charWidth } = layout.metrics;
13
+ const document = layout.document;
14
+ const fromLine = document.positionAt(layout.hitTest(from.x, from.y)).line;
15
+ const toLine = document.positionAt(layout.hitTest(to.x, to.y)).line;
16
+ const fromCell = cellAt(layout, from.x);
17
+ const toCell = cellAt(layout, to.x);
18
+ const left = Math.min(fromCell, toCell);
19
+ const right = Math.max(fromCell, toCell);
20
+ const reversed = toCell < fromCell;
21
+ const result = [];
22
+ for (let line = Math.min(fromLine, toLine); line <= Math.max(fromLine, toLine); line++) {
23
+ const row = layout.rowForLine(line);
24
+ if (row.kind !== 'text' || layout.isHidden(line)) {
25
+ continue;
26
+ }
27
+ const geometry = layout.geometry(row);
28
+ const endX = geometry.rowStarts.length > 1 ? geometry.rowEnds[0] : geometry.before[geometry.before.length - 1];
29
+ const offsetAt = (cell) => layout.offsetAtX(geometry, 0, TEXT_PADDING + cell * charWidth).offset;
30
+ if (cellAt(layout, endX + TEXT_PADDING) < left) {
31
+ if (left === right) {
32
+ const end = document.getLine(line).end;
33
+ result.push({ anchor: end, head: end });
34
+ }
35
+ continue;
36
+ }
37
+ const start = offsetAt(left);
38
+ const end = offsetAt(right);
39
+ result.push(reversed ? { anchor: end, head: start } : { anchor: start, head: end });
40
+ }
41
+ if (result.length === 0) {
42
+ const end = document.getLine(toLine).end;
43
+ result.push({ anchor: end, head: end });
44
+ }
45
+ return result;
46
+ }
47
+ /* Whether two points are in the same cell of the same line, which makes a press with alt a click and not yet a box. */
48
+ export function sameCell(layout, from, to) {
49
+ const document = layout.document;
50
+ return (cellAt(layout, from.x) === cellAt(layout, to.x) &&
51
+ document.positionAt(layout.hitTest(from.x, from.y)).line === document.positionAt(layout.hitTest(to.x, to.y)).line);
52
+ }
@@ -0,0 +1,64 @@
1
+ import type { Keymap } from './keymap-table.ts';
2
+ import type { EditorClickHandler, EditorContextMenu, EditorKeyHandler, EditorRunCommand, EditorSelectionRanges, KeyChord } from './types.ts';
3
+ import type { EditorView } from './view.ts';
4
+ export interface ControllerOptions {
5
+ keymap?: Keymap;
6
+ handBack: readonly KeyChord[];
7
+ apple: boolean;
8
+ save(): void;
9
+ blur(): void;
10
+ }
11
+ export declare class InputController {
12
+ private readonly native;
13
+ private readonly pointer;
14
+ private readonly cleanup;
15
+ private readonly keyHandlers;
16
+ private readonly gesture;
17
+ private readonly clickHandlers;
18
+ private readonly contextListeners;
19
+ private readonly subscription;
20
+ private historyGroup;
21
+ private block;
22
+ private selectionRanges;
23
+ private expansions;
24
+ private lastInputAt;
25
+ private desiredXs;
26
+ private keepDesiredXs;
27
+ private readonly view;
28
+ private readonly options;
29
+ constructor(view: EditorView, options: ControllerOptions);
30
+ private listen;
31
+ private attach;
32
+ dispose(): void;
33
+ private blockedByReadOnly;
34
+ private inputGroup;
35
+ private applyEdits;
36
+ private replaceSelections;
37
+ private paste;
38
+ private commandOptions;
39
+ private viewCommand;
40
+ command(name: EditorRunCommand): boolean;
41
+ setSelectionRanges(provider: EditorSelectionRanges | null): void;
42
+ private expandThrough;
43
+ private modelCommand;
44
+ private typeText;
45
+ private movePages;
46
+ private toggleColumnMode;
47
+ private extendBlock;
48
+ private moveCarets;
49
+ private dropText;
50
+ private goToMatchingBracket;
51
+ private run;
52
+ onKeyDown(handler: EditorKeyHandler): () => void;
53
+ onClick(handler: EditorClickHandler): () => void;
54
+ onContextMenu(listener: (menu: EditorContextMenu) => void): () => void;
55
+ private keydown;
56
+ private beforeInput;
57
+ private copy;
58
+ private pointerDown;
59
+ private takenByHost;
60
+ private positionOf;
61
+ private contextMenu;
62
+ private stickyDown;
63
+ private gutterDown;
64
+ }