dsh-remote-vs 0.0.0-stage → 0.8.3

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +385 -0
  3. package/README.md +347 -2
  4. package/cordis.patch.yml +259 -0
  5. package/icon.svg +7 -0
  6. package/lib/client.js +62602 -0
  7. package/lib/index.js +4287 -0
  8. package/lib/p2-bridge-refs.js +10 -0
  9. package/lib/p2-bridge.js +16 -0
  10. package/lib/p2-router-fs.js +252 -0
  11. package/lib/p2-router-refs.js +466 -0
  12. package/lib/p2-router-spill.js +62 -0
  13. package/lib/p2-router-subprocess.js +64 -0
  14. package/lib/p2-shell-bash.js +24 -0
  15. package/lib/ssh-proc-arI5lm4W.js +632 -0
  16. package/lib/types/changes/recorder.d.ts +240 -0
  17. package/lib/types/changes/status.d.ts +201 -0
  18. package/lib/types/changes/text-diff.d.ts +65 -0
  19. package/lib/types/client/CodeEditor.d.ts +125 -0
  20. package/lib/types/client/Dialog.d.ts +14 -0
  21. package/lib/types/client/DirectoryFlow.d.ts +30 -0
  22. package/lib/types/client/EditorPane.d.ts +30 -0
  23. package/lib/types/client/HostsView.d.ts +10 -0
  24. package/lib/types/client/NativeExplorer.d.ts +39 -0
  25. package/lib/types/client/RemoteDirectoryPicker.d.ts +16 -0
  26. package/lib/types/client/RemoteEditorView.d.ts +20 -0
  27. package/lib/types/client/RemoteExplorer.d.ts +18 -0
  28. package/lib/types/client/RemoteRoot.d.ts +16 -0
  29. package/lib/types/client/SearchView.d.ts +14 -0
  30. package/lib/types/client/SettingsHosts.d.ts +15 -0
  31. package/lib/types/client/TerminalView.d.ts +16 -0
  32. package/lib/types/client/WorkspaceWizard.d.ts +16 -0
  33. package/lib/types/client/WorkspacesView.d.ts +14 -0
  34. package/lib/types/client/XTerm.d.ts +15 -0
  35. package/lib/types/client/api.d.ts +162 -0
  36. package/lib/types/client/changes/ChangesDock.d.ts +35 -0
  37. package/lib/types/client/changes/model.d.ts +36 -0
  38. package/lib/types/client/changes/undo-affordance.d.ts +48 -0
  39. package/lib/types/client/changes/undo-context.d.ts +25 -0
  40. package/lib/types/client/composer.d.ts +165 -0
  41. package/lib/types/client/i18n.d.ts +17 -0
  42. package/lib/types/client/index.d.ts +14 -0
  43. package/lib/types/client/remote-file-open-target.d.ts +247 -0
  44. package/lib/types/client/remote-file-open.d.ts +35 -0
  45. package/lib/types/client/reveal.d.ts +15 -0
  46. package/lib/types/client/scm/DiffView.d.ts +24 -0
  47. package/lib/types/client/scm/HistoryPanel.d.ts +12 -0
  48. package/lib/types/client/scm/RepoPicker.d.ts +24 -0
  49. package/lib/types/client/scm/ScmPanel.d.ts +15 -0
  50. package/lib/types/client/scm/actions.d.ts +170 -0
  51. package/lib/types/client/scm/diff-model.d.ts +29 -0
  52. package/lib/types/client/scm/stage-queue.d.ts +99 -0
  53. package/lib/types/client/scm/status-map.d.ts +157 -0
  54. package/lib/types/client/scm/untracked-dirs.d.ts +72 -0
  55. package/lib/types/client/selection-popup.d.ts +61 -0
  56. package/lib/types/client/store.d.ts +437 -0
  57. package/lib/types/client/theme.d.ts +15 -0
  58. package/lib/types/client/ui.d.ts +46 -0
  59. package/lib/types/client/upload-plan.d.ts +78 -0
  60. package/lib/types/client/upload-source.d.ts +46 -0
  61. package/lib/types/client/use-remote-chat-target.d.ts +17 -0
  62. package/lib/types/client/vsc/FindWidget.d.ts +81 -0
  63. package/lib/types/client/vsc/change-marks.d.ts +150 -0
  64. package/lib/types/client/vsc/editor-langs.d.ts +5 -0
  65. package/lib/types/client/vsc/editor-palette.d.ts +37 -0
  66. package/lib/types/client/vsc/editor-theme.d.ts +32 -0
  67. package/lib/types/client/vsc/fallback-highlight.d.ts +26 -0
  68. package/lib/types/client/vsc/kit.d.ts +195 -0
  69. package/lib/types/client/vsc/markdown.d.ts +11 -0
  70. package/lib/types/client/vsc/reveal-line.d.ts +38 -0
  71. package/lib/types/client/vsc/tokenize.d.ts +104 -0
  72. package/lib/types/deploy.d.ts +94 -0
  73. package/lib/types/fs-ops.d.ts +8 -0
  74. package/lib/types/git-format.d.ts +34 -0
  75. package/lib/types/git-ops.d.ts +183 -0
  76. package/lib/types/http.d.ts +14 -0
  77. package/lib/types/importers.d.ts +20 -0
  78. package/lib/types/index.d.ts +87 -0
  79. package/lib/types/keygen.d.ts +19 -0
  80. package/lib/types/loopback.d.ts +7 -0
  81. package/lib/types/p2/bridge-refs.d.ts +21 -0
  82. package/lib/types/p2/bridge.d.ts +24 -0
  83. package/lib/types/p2/file-refs.d.ts +73 -0
  84. package/lib/types/p2/helper-rpc.d.ts +63 -0
  85. package/lib/types/p2/in-flight-gate.d.ts +25 -0
  86. package/lib/types/p2/router-fs.d.ts +92 -0
  87. package/lib/types/p2/router-refs.d.ts +45 -0
  88. package/lib/types/p2/router-spill.d.ts +11 -0
  89. package/lib/types/p2/router-subprocess.d.ts +23 -0
  90. package/lib/types/p2/shell-bash.d.ts +16 -0
  91. package/lib/types/p2/ssh-proc.d.ts +22 -0
  92. package/lib/types/p2/worlds.d.ts +179 -0
  93. package/lib/types/paths.d.ts +12 -0
  94. package/lib/types/pool.d.ts +105 -0
  95. package/lib/types/protocol.d.ts +452 -0
  96. package/lib/types/routes.d.ts +27 -0
  97. package/lib/types/search-ops.d.ts +68 -0
  98. package/lib/types/sftp.d.ts +90 -0
  99. package/lib/types/store.d.ts +66 -0
  100. package/lib/types/terminal.d.ts +51 -0
  101. package/lib/types/timeouts.d.ts +83 -0
  102. package/lib/types/tools.d.ts +41 -0
  103. package/lib/worlds-PUumRIh7.js +18765 -0
  104. package/locale/en.json +6 -0
  105. package/locale/zh.json +6 -0
  106. package/package.json +189 -4
  107. package/src/changes/recorder.ts +902 -0
  108. package/src/changes/status.ts +448 -0
  109. package/src/changes/text-diff.ts +377 -0
  110. package/src/client/CodeEditor.tsx +923 -0
  111. package/src/client/Dialog.tsx +76 -0
  112. package/src/client/DirectoryFlow.tsx +431 -0
  113. package/src/client/EditorPane.tsx +512 -0
  114. package/src/client/HostsView.tsx +312 -0
  115. package/src/client/NativeExplorer.tsx +121 -0
  116. package/src/client/RemoteDirectoryPicker.tsx +157 -0
  117. package/src/client/RemoteEditorView.tsx +54 -0
  118. package/src/client/RemoteExplorer.tsx +1398 -0
  119. package/src/client/RemoteRoot.tsx +389 -0
  120. package/src/client/SearchView.tsx +407 -0
  121. package/src/client/SettingsHosts.tsx +136 -0
  122. package/src/client/TerminalView.tsx +308 -0
  123. package/src/client/WorkspaceWizard.tsx +199 -0
  124. package/src/client/WorkspacesView.tsx +93 -0
  125. package/src/client/XTerm.tsx +308 -0
  126. package/src/client/api.ts +331 -0
  127. package/src/client/changes/ChangesDock.tsx +348 -0
  128. package/src/client/changes/changes.css +230 -0
  129. package/src/client/changes/model.ts +347 -0
  130. package/src/client/changes/undo-affordance.ts +389 -0
  131. package/src/client/changes/undo-context.ts +34 -0
  132. package/src/client/composer.ts +436 -0
  133. package/src/client/css.d.ts +7 -0
  134. package/src/client/i18n.ts +702 -0
  135. package/src/client/index.tsx +286 -0
  136. package/src/client/remote-file-open-target.ts +553 -0
  137. package/src/client/remote-file-open.ts +261 -0
  138. package/src/client/reveal.ts +33 -0
  139. package/src/client/scm/DiffView.tsx +570 -0
  140. package/src/client/scm/HistoryPanel.tsx +353 -0
  141. package/src/client/scm/RepoPicker.tsx +238 -0
  142. package/src/client/scm/ScmPanel.tsx +857 -0
  143. package/src/client/scm/actions.ts +460 -0
  144. package/src/client/scm/diff-model.ts +206 -0
  145. package/src/client/scm/stage-queue.ts +264 -0
  146. package/src/client/scm/status-map.ts +381 -0
  147. package/src/client/scm/untracked-dirs.ts +139 -0
  148. package/src/client/selection-popup.ts +157 -0
  149. package/src/client/store.tsx +1670 -0
  150. package/src/client/theme.ts +54 -0
  151. package/src/client/ui.tsx +136 -0
  152. package/src/client/upload-plan.ts +153 -0
  153. package/src/client/upload-source.ts +145 -0
  154. package/src/client/use-remote-chat-target.ts +153 -0
  155. package/src/client/vsc/FindWidget.tsx +446 -0
  156. package/src/client/vsc/change-marks.ts +586 -0
  157. package/src/client/vsc/editor-langs.ts +137 -0
  158. package/src/client/vsc/editor-palette.ts +107 -0
  159. package/src/client/vsc/editor-theme.ts +93 -0
  160. package/src/client/vsc/fallback-highlight.ts +145 -0
  161. package/src/client/vsc/kit.tsx +777 -0
  162. package/src/client/vsc/markdown.tsx +172 -0
  163. package/src/client/vsc/reveal-line.ts +142 -0
  164. package/src/client/vsc/tokenize.ts +569 -0
  165. package/src/client/vsc/vsc.css +2669 -0
  166. package/src/deploy.ts +481 -0
  167. package/src/fs-ops.ts +57 -0
  168. package/src/git-format.ts +205 -0
  169. package/src/git-ops.ts +693 -0
  170. package/src/http.ts +55 -0
  171. package/src/importers.ts +141 -0
  172. package/src/index.ts +442 -0
  173. package/src/keygen.ts +77 -0
  174. package/src/loopback.ts +47 -0
  175. package/src/p2/bridge-refs.ts +27 -0
  176. package/src/p2/bridge.ts +32 -0
  177. package/src/p2/file-refs.ts +392 -0
  178. package/src/p2/helper-rpc.ts +394 -0
  179. package/src/p2/in-flight-gate.ts +63 -0
  180. package/src/p2/router-fs.ts +327 -0
  181. package/src/p2/router-refs.ts +166 -0
  182. package/src/p2/router-spill.ts +75 -0
  183. package/src/p2/router-subprocess.ts +92 -0
  184. package/src/p2/shell-bash.ts +30 -0
  185. package/src/p2/ssh-proc.ts +802 -0
  186. package/src/p2/worlds.ts +735 -0
  187. package/src/paths.ts +50 -0
  188. package/src/pool.ts +811 -0
  189. package/src/protocol.ts +460 -0
  190. package/src/routes.ts +802 -0
  191. package/src/search-ops.ts +220 -0
  192. package/src/sftp.ts +677 -0
  193. package/src/store.ts +364 -0
  194. package/src/terminal.ts +291 -0
  195. package/src/timeouts.ts +122 -0
  196. package/src/tools.ts +320 -0
  197. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.helper.json +16 -0
  198. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz +4 -0
  199. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz.sha256 +1 -0
  200. package/vendor/dsh-remote-helpers/glibc-ceiling.patch +31 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Minimal, dependency-free Markdown renderer for the remote preview pane.
3
+ *
4
+ * It renders React elements directly (never `dangerouslySetInnerHTML`), so raw
5
+ * HTML in a remote document stays inert text. Coverage is the subset that
6
+ * matters when reading a repository file: headings, fenced and inline code,
7
+ * lists, blockquotes, rules, GFM tables, links, bold / italic / strike.
8
+ */
9
+ import { createElement, type ReactNode } from 'react'
10
+
11
+ /**
12
+ * Schemes a remote document's link may use.
13
+ *
14
+ * The preview renders UNTRUSTED content: any repository the user browses (or
15
+ * a file an agent wrote) can contain `[x](javascript:...)`, which matches the
16
+ * inline pattern below unchanged. React 18 only warns about a `javascript:`
17
+ * href — it does not strip the attribute — and such a URL executes in the
18
+ * current document regardless of `target`, i.e. in the DSH GUI's own origin.
19
+ * Anything not matching is rendered as plain text instead of a link.
20
+ */
21
+ const SAFE_HREF = /^(?:https?:|mailto:|#|\/(?!\/)|\.{1,2}\/)/i
22
+
23
+ /** Inline formatting: code spans, links, bold, italic, strike. */
24
+ function inline(text: string, keyBase: string): ReactNode[] {
25
+ const nodes: ReactNode[] = []
26
+ const pattern = /(`[^`]+`)|(\*\*[^*]+\*\*)|(__[^_]+__)|(\*[^*\n]+\*)|(_[^_\n]+_)|(~~[^~]+~~)|(\[[^\]]+\]\([^)\s]+\))/g
27
+ let last = 0
28
+ let index = 0
29
+ for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) {
30
+ if (match.index > last) nodes.push(text.slice(last, match.index))
31
+ const token = match[0]
32
+ const key = `${keyBase}-i${index++}`
33
+ if (token.startsWith('`')) {
34
+ nodes.push(createElement('code', { key }, token.slice(1, -1)))
35
+ } else if (token.startsWith('**') || token.startsWith('__')) {
36
+ nodes.push(createElement('strong', { key }, token.slice(2, -2)))
37
+ } else if (token.startsWith('~~')) {
38
+ nodes.push(createElement('del', { key }, token.slice(2, -2)))
39
+ } else if (token.startsWith('[')) {
40
+ const split = token.indexOf('](')
41
+ const label = token.slice(1, split)
42
+ const href = token.slice(split + 2, -1)
43
+ if (SAFE_HREF.test(href)) {
44
+ nodes.push(createElement('a', { key, href, target: '_blank', rel: 'noreferrer noopener' }, label))
45
+ } else {
46
+ // Unsafe scheme (javascript:, data:, vbscript:, …): keep the text, drop
47
+ // the link. Skipping the token inside a single createElement pair keeps
48
+ // the label readable but inert.
49
+ nodes.push(createElement('span', { key, title: href }, label))
50
+ }
51
+ } else {
52
+ nodes.push(createElement('em', { key }, token.slice(1, -1)))
53
+ }
54
+ last = match.index + token.length
55
+ }
56
+ if (last < text.length) nodes.push(text.slice(last))
57
+ return nodes
58
+ }
59
+
60
+ /** Split one table row into cells. */
61
+ function cells(line: string): string[] {
62
+ return line.replace(/^\s*\|/, '').replace(/\|\s*$/, '').split('|').map(cell => cell.trim())
63
+ }
64
+
65
+ /** Render a whole document. */
66
+ export function renderMarkdown(text: string): ReactNode {
67
+ const lines = text.replace(/\r\n/g, '\n').split('\n')
68
+ const out: ReactNode[] = []
69
+ let i = 0
70
+ let key = 0
71
+ const nextKey = (): string => `md-${key++}`
72
+
73
+ while (i < lines.length) {
74
+ const line = lines[i] as string
75
+
76
+ // Fenced code.
77
+ const fence = /^\s*(```|~~~)\s*([\w+-]*)\s*$/.exec(line)
78
+ if (fence !== null) {
79
+ const marker = fence[1] as string
80
+ const body: string[] = []
81
+ i += 1
82
+ while (i < lines.length && !(lines[i] as string).trimStart().startsWith(marker)) {
83
+ body.push(lines[i] as string)
84
+ i += 1
85
+ }
86
+ i += 1
87
+ out.push(createElement('pre', { key: nextKey() }, createElement('code', null, body.join('\n'))))
88
+ continue
89
+ }
90
+
91
+ // Heading.
92
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line)
93
+ if (heading !== null) {
94
+ const level = Math.min(6, (heading[1] as string).length)
95
+ out.push(createElement(`h${level}`, { key: nextKey() }, inline(heading[2] as string, nextKey())))
96
+ i += 1
97
+ continue
98
+ }
99
+
100
+ // Horizontal rule.
101
+ if (/^\s*([-*_])\s*\1\s*\1[\s\S]*$/.test(line) && line.replace(/[\s\-*_]/g, '') === '') {
102
+ out.push(createElement('hr', { key: nextKey() }))
103
+ i += 1
104
+ continue
105
+ }
106
+
107
+ // Table (header row + separator).
108
+ if (line.includes('|') && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|[\s:|-]*$/.test(lines[i + 1] as string)) {
109
+ const head = cells(line)
110
+ i += 2
111
+ const rows: string[][] = []
112
+ while (i < lines.length && (lines[i] as string).includes('|') && (lines[i] as string).trim() !== '') {
113
+ rows.push(cells(lines[i] as string))
114
+ i += 1
115
+ }
116
+ out.push(createElement('table', { key: nextKey() },
117
+ createElement('thead', null,
118
+ createElement('tr', null, head.map((cell, index) => createElement('th', { key: index }, inline(cell, nextKey()))))),
119
+ createElement('tbody', null, rows.map((row, rowIndex) => createElement('tr', { key: rowIndex },
120
+ row.map((cell, cellIndex) => createElement('td', { key: cellIndex }, inline(cell, nextKey()))))))))
121
+ continue
122
+ }
123
+
124
+ // Blockquote.
125
+ if (/^\s*>/.test(line)) {
126
+ const body: string[] = []
127
+ while (i < lines.length && /^\s*>/.test(lines[i] as string)) {
128
+ body.push((lines[i] as string).replace(/^\s*>\s?/, ''))
129
+ i += 1
130
+ }
131
+ out.push(createElement('blockquote', { key: nextKey() }, renderMarkdown(body.join('\n'))))
132
+ continue
133
+ }
134
+
135
+ // Lists.
136
+ const listMatch = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(line)
137
+ if (listMatch !== null) {
138
+ const ordered = /^\d/.test(listMatch[2] as string)
139
+ const items: string[] = []
140
+ while (i < lines.length) {
141
+ const current = /^(\s*)([-*+]|\d+[.)])\s+(.*)$/.exec(lines[i] as string)
142
+ if (current === null) break
143
+ items.push(current[3] as string)
144
+ i += 1
145
+ }
146
+ out.push(createElement(ordered ? 'ol' : 'ul', { key: nextKey() },
147
+ items.map((item, index) => createElement('li', { key: index }, inline(item, nextKey())))))
148
+ continue
149
+ }
150
+
151
+ // Blank line.
152
+ if (line.trim() === '') {
153
+ i += 1
154
+ continue
155
+ }
156
+
157
+ // Paragraph: consume until a blank line or another block starter.
158
+ const paragraph: string[] = [line]
159
+ i += 1
160
+ while (i < lines.length) {
161
+ const current = lines[i] as string
162
+ if (current.trim() === '') break
163
+ if (/^(#{1,6})\s/.test(current) || /^\s*(```|~~~)/.test(current) || /^\s*>/.test(current)) break
164
+ if (/^(\s*)([-*+]|\d+[.)])\s+/.test(current)) break
165
+ paragraph.push(current)
166
+ i += 1
167
+ }
168
+ out.push(createElement('p', { key: nextKey() }, inline(paragraph.join(' '), nextKey())))
169
+ }
170
+
171
+ return createElement('div', { className: 'rvs-markdown' }, out)
172
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * "Land here": the jump a search hit (or an `@`-reference) performs.
3
+ *
4
+ * Kept as one module-level helper because it has to work on an editor that is
5
+ * ALREADY mounted — clicking a second hit in the same file must re-jump, not
6
+ * silently do nothing (which is what a mount-only reveal does).
7
+ */
8
+ import { EditorSelection, StateEffect, StateField } from '@codemirror/state'
9
+ import { Decoration, EditorView, type DecorationSet } from '@codemirror/view'
10
+
11
+ /** Set (or clear) the landing highlight on one line. */
12
+ export const setRevealLine = StateEffect.define<number | null>()
13
+
14
+ /**
15
+ * The landing highlight for a jump (search hit, `@` reference): without it a
16
+ * jump into a long file leaves the caret in a wall of identical lines and the
17
+ * user has to hunt for it.
18
+ */
19
+ export const revealLineField = StateField.define<DecorationSet>({
20
+ create: () => Decoration.none,
21
+ update(value, tr) {
22
+ let next = value.map(tr.changes)
23
+ for (const effect of tr.effects) {
24
+ if (!effect.is(setRevealLine)) continue
25
+ next = effect.value === null
26
+ ? Decoration.none
27
+ : Decoration.set([Decoration.line({ class: 'rvs-reveal-line' }).range(effect.value)])
28
+ }
29
+ return next
30
+ },
31
+ provide: field => EditorView.decorations.from(field),
32
+ })
33
+
34
+ /** How long the landing highlight stays lit. */
35
+ const LIT_MS = 2600
36
+
37
+ /**
38
+ * Scroll to a 1-based line, put the caret on it and light it up.
39
+ *
40
+ * Centering is done in TWO steps on purpose: CodeMirror scrolls its own
41
+ * scroller, but the editor can also sit inside a taller ancestor scroller (the
42
+ * panel height is not ours to control), and only `scrollIntoView` walks every
43
+ * scrollable ancestor. Without the second step the line can land off-screen.
44
+ */
45
+ export function revealLine(view: EditorView, target: number, lit: (position: number | null) => void): void {
46
+ let line = 0
47
+ try {
48
+ const clamped = Math.min(Math.max(1, Math.round(target)), view.state.doc.lines)
49
+ line = view.state.doc.line(clamped).number
50
+ const from = view.state.doc.line(line).from
51
+ view.dispatch({
52
+ selection: { anchor: from },
53
+ effects: EditorView.scrollIntoView(from, { y: 'center' }),
54
+ })
55
+ lit(from)
56
+ } catch {
57
+ // A stale line number or a disposed view: nothing to reveal.
58
+ return
59
+ }
60
+ view.focus()
61
+ // Second step: centre the line in the *visible* area whatever scrolls.
62
+ window.setTimeout(() => {
63
+ try {
64
+ const element = lineElement(view, line)
65
+ element?.scrollIntoView({ block: 'center', inline: 'nearest' })
66
+ } catch {
67
+ // No layout: the CodeMirror scroll already did its best.
68
+ }
69
+ }, 30)
70
+ window.setTimeout(() => {
71
+ try {
72
+ lit(null)
73
+ } catch {
74
+ // Disposed.
75
+ }
76
+ }, LIT_MS)
77
+ }
78
+
79
+ /** The rendered `.cm-line` element for a 1-based line number, if visible. */
80
+ function lineElement(view: EditorView, line: number): HTMLElement | null {
81
+ const position = view.state.doc.line(line).from
82
+ if (position < view.viewport.from || position > view.viewport.to) return null
83
+ const dom = view.domAtPos(position)
84
+ let node: Node | null = dom.node
85
+ while (node !== null && node !== view.dom) {
86
+ if (node instanceof HTMLElement && node.classList.contains('cm-line')) return node
87
+ node = node.parentNode
88
+ }
89
+ return null
90
+ }
91
+
92
+ /**
93
+ * 选中一个行区间并把它带到视野中央(「上一个/下一个改动点」用)。
94
+ *
95
+ * 与 `revealLine` 的差别只有两点:用 `EditorSelection.range` 把整段选起来(一个
96
+ * 改动块常常有好几行,只放一个 caret 看不出这次改动有多大),以及**不**点灯 —
97
+ * 改动块本身已经有自己的绿/红背景,再叠一层 landing 蓝只会糊成第三种颜色。
98
+ *
99
+ * `from` / `to` 都是 **1-based 行号**(与 `revealLine` 同一套语义),越界夹到文件
100
+ * 首尾,`from > to` 时自动对调。`lit` 与 `revealLine` 同义,给那些需要额外提示
101
+ * 的调用方留口子(传 null 收灯,`LIT_MS` 后自动熄灭)。
102
+ */
103
+ export function revealRange(
104
+ view: EditorView,
105
+ from: number,
106
+ to: number,
107
+ lit?: (position: number | null) => void,
108
+ ): void {
109
+ let line = 0
110
+ try {
111
+ const total = view.state.doc.lines
112
+ const firstNumber = Math.min(Math.max(1, Math.round(Math.min(from, to))), total)
113
+ const lastNumber = Math.min(Math.max(firstNumber, Math.round(Math.max(from, to))), total)
114
+ const first = view.state.doc.line(firstNumber)
115
+ const last = view.state.doc.line(lastNumber)
116
+ line = firstNumber
117
+ view.dispatch({
118
+ selection: EditorSelection.range(first.from, last.to),
119
+ effects: EditorView.scrollIntoView(first.from, { y: 'center' }),
120
+ })
121
+ lit?.(first.from)
122
+ } catch {
123
+ // A stale line number or a disposed view: nothing to reveal.
124
+ return
125
+ }
126
+ view.focus()
127
+ // 与 revealLine 同理:祖先滚动容器也要走一遍,否则选中的块可能整个在视野外。
128
+ window.setTimeout(() => {
129
+ try {
130
+ lineElement(view, line)?.scrollIntoView({ block: 'center', inline: 'nearest' })
131
+ } catch {
132
+ // No layout: the CodeMirror scroll already did its best.
133
+ }
134
+ }, 30)
135
+ window.setTimeout(() => {
136
+ try {
137
+ lit?.(null)
138
+ } catch {
139
+ // Disposed.
140
+ }
141
+ }, LIT_MS)
142
+ }