turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md · 103 lines · 7.9 KBmarkdown Blame HistoryRaw
🛟 Updated. 28d5985 k33g 16h ago1# editor
2
3The text-editing widget: a scrolling, colouring viewport onto one buffer.
4
5This is where the pure packages meet the screen — `buffer` holds the text, `syntax` colours it, `theme` says in what, `ui` draws it — and it is the only place that knows how all four fit together.
6
7## What a view draws
8
9```
10 1 package main ▲ ← line-number gutter, then the text, then
11 2 ▓ the vertical scroll bar
12 3 import "fmt" ░
13◄▓░░░░░░░░░░░░░░░░░░░░░░░► ← the horizontal one along the bottom
14```
15
16Each character is drawn in its **syntax colour**, unless the selection covers it. On the cursor's own line the syntax colours keep their foreground but take the current-line background, so the highlight shows *through* the coloured tokens instead of being punched full of holes.
17
18Tabs are expanded to the buffer's tab width, and the view scrolls in both directions to keep the cursor visible.
19
20The **cursor is marked twice**. The theme's `editor.cursor` background is sent to the terminal as the colour of its *own* cursor, and the cell underneath is painted in the same style as a fallback for terminals that ignore that. Painting alone is not enough: a terminal draws its cursor over the cell, in whatever colour the user configured for some other palette, so on a dark theme it is very often a dark cursor covering whatever is beneath it. Only the terminal cursor's own colour can win, and only the theme should decide it.
21
22The colours must be a **distinct pair**, never a reversal of the line — terminals that draw their cursor by inverting the cell would invert it straight back into invisibility. Tests hold every theme to that, and to a minimum distance from the line it sits on. Only the **active** window shows a cursor: a window tells its content whether it is focused.
23
24The theme-arithmetic tests live here for the same reason the drawing does: this is where colour meets the screen. Two measures, and both are needed. **Channel distance** answers "can the eye see these are two colours" — right for a cursor, a highlight, a selection. **WCAG relative luminance** answers "can this be read" — right for prose, and the reason `syntax.comment` is held to 4.5:1 in every theme this project authors. Turbo Classic's comments cleared the first by a mile and failed the second, and were hard to read for exactly that long.
25
26## Keys
27
28Turbo C's bindings where they still make sense, modern ones where they do not.
29
30| | |
31| --- | --- |
32| Arrows, `Home`, `End`, `PgUp`, `PgDn` | Move |
33| `Ctrl-←` `Ctrl-→` | By word |
34| `Ctrl-Home` `Ctrl-End` | Start and end of the file |
35| `Shift`+any movement | Extend the selection |
36| `Enter` | New line, keeping the indent |
37| `Tab` / `Shift-Tab` | Indent / unindent — the whole selection when there is one |
38| `Ctrl-Z` `Ctrl-Y` | Undo, redo |
39| `Ctrl-C` `Ctrl-X` `Ctrl-V` | Clipboard |
40| `Ctrl-Ins` `Shift-Del` `Shift-Ins` | The same, spelled the Turbo C way |
41| `Ctrl-A` | Select all |
42| `Ctrl-Space` | Ask for completion |
43
44Typing a `.` asks for completion too, since that is where the list is most useful. Mouse: click to place the cursor, drag to select, wheel to scroll.
45
46## Public API
47
48| | |
49| --- | --- |
50| `NewView(buf, clipboard) *View` | A view onto a buffer, sharing a clipboard with the other views |
51| `(*View) Buffer() *buffer.Buffer` | The text being edited |
52| `(*View) Draw`, `HandleKey`, `HandleMouse`, `Bounds`, `SetBounds` | The `ui.Widget` contract |
53| `(*View) Cut/Copy/Paste/Undo/Redo/SelectAll() bool` | What the Edit menu calls |
54| `(*View) Indent() / Unindent()` | Block indentation, one undo step |
55| `(*View) InsertSnippet(string)` | Insert text at the cursor, re-indenting the lines after the first to match the current line; one undo step |
56| `(*View) GoToLine(int)` | Jump, counting from one |
57| `(*View) EnsureCursorVisible()` | Scroll so the cursor is on screen |
58| `(*View) CursorStatus() string` | The `line:column` the status bar shows |
59| `(*View) WordBeforeCursor() string` | The identifier being typed — what a completion list filters on |
60| `(*View) ReplaceWordBeforeCursor(string)` | Accept a completion |
61| `(*View) LineNumbers() / SetLineNumbers(bool)` | Show or hide the gutter |
62| `(*View) RefreshSyntax()` | Re-decide whether this file can be coloured, after a rename |
63| `(*View) SetMarks(map[int]Severity)`, `(*View) Marks()` | Which lines have something wrong with them |
64| `(*View) InsertLine()`, `(*View) DeleteLine()` | Turbo C's Ctrl-N and Ctrl-Y |
65| `(*View) SetClock(func() time.Time)` | The clock two clicks are timed against; only a test calls it |
66| `Clipboard` | The text shared between views by cut, copy and paste |
67
68Three callbacks let the app follow along without the view knowing what an app is: `OnChange`, `OnCursorMove`, `OnCompletionRequest`.
69
70## Why a snippet is re-indented
71
72`InsertSnippet` copies the current line's leading whitespace onto every line of the text after the first. Inserting a multi-line body verbatim restarts it at column zero, which is wrong everywhere except the top level of a file — and an `if err != nil` is inserted inside something by definition. Copying the line's *own* prefix follows whatever the file already uses, tabs or spaces, rather than imposing a choice.
73
74A blank line in the body stays blank: padding it to the indent leaves trailing whitespace that every formatter then strips, which is noise in the very next diff.
75
76## Why its own clipboard
77
78A terminal program cannot read the host's clipboard portably. A clipboard shared between the editor's own windows is what Turbo C offered, and it is what this offers.
79
80## Tests
81
82```sh
83make test
84go test ./editor/
85```
86
87## Marks, and why this package has never heard of a language server
88
89A mark is a character in the gutter beside a line something is wrong with. The severities are `editor.Severity` — this package's own type — and not the protocol's, because this package colours and draws text and knows nothing about protocols. Whoever holds the diagnostics translates on the way in. The alternative was importing `lsp` here to draw one character, which would put the protocol underneath the drawing.
90
91The mark goes in the column that **separates the line numbers from the text**. The gutter is already `digits + 1` wide, the +1 being that separator, so a mark costs no layout: not the text's left edge, not the cursor's screen column, not the column a mouse click lands on. The consequence, stated where anyone changing it will read it: **hiding the line numbers hides the marks.** The alternative — a one-column gutter appearing when the numbers are off — would make the text jump sideways the moment a server reported anything.
92
93The three `diagnostic.*` theme keys are what they are drawn in. They had been defined in every shipped theme, tested for readability, and drawn nowhere at all.
94
95## Counting clicks
96
97A second press on the **same cell** within 400 ms selects the word under it. Same cell, not merely near: a press one column over is a different word, and selecting the first because the pointer drifted is worse than selecting nothing.
98
99Two details are what make this work rather than nearly work. tcell sends `Button1` for every event of a **drag** as well as for a press, so clicks are counted only when no drag is in progress — otherwise a slow drag becomes a double click halfway through. And the clock is a **field**, set with `SetClock`, for the same reason `app.App` has one: a double click measured against the real clock is a test that passes or fails by how fast the machine is.
100
101After a double click the drag carries on from the word, so holding and moving extends from the **start of the word**, by character. That is not the word-by-word drag a large editor does; it is the cheap half of it, and it beats the alternative, which is the word collapsing back to a single character the moment the pointer twitches.
102
103The counter keeps going past two, so a triple click can be given a meaning later without reworking any of this.