@peaske7/readit 0.1.8 → 0.2.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 (221) hide show
  1. package/.claude/CLAUDE.md +118 -76
  2. package/.claude/commands/review.md +1 -1
  3. package/.claude/roadmap.md +32 -9
  4. package/.claude/user-stories.md +100 -15
  5. package/AGENTS.md +30 -26
  6. package/Makefile +32 -0
  7. package/README.md +90 -5
  8. package/biome.json +18 -8
  9. package/bun.lock +426 -710
  10. package/bunfig.toml +2 -0
  11. package/docs/perf-baseline.md +130 -0
  12. package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
  13. package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
  14. package/e2e/comments.spec.ts +14 -58
  15. package/e2e/document-load.spec.ts +1 -23
  16. package/e2e/export.spec.ts +4 -4
  17. package/e2e/perf/add-comment.spec.ts +116 -0
  18. package/e2e/perf/fixtures/generate.ts +327 -0
  19. package/e2e/perf/initial-load.spec.ts +49 -0
  20. package/e2e/perf/perf.setup.ts +23 -0
  21. package/e2e/perf/perf.teardown.ts +9 -0
  22. package/e2e/perf/screenshot-final.png +0 -0
  23. package/e2e/perf/scroll.spec.ts +39 -0
  24. package/e2e/perf/tab-switch.spec.ts +69 -0
  25. package/e2e/perf/text-selection.spec.ts +119 -0
  26. package/e2e/perf/utils/metrics.ts +350 -0
  27. package/e2e/perf/utils/perf-cli.ts +86 -0
  28. package/e2e/persistence-file.spec.ts +41 -26
  29. package/e2e/utils/selection.ts +17 -73
  30. package/go/cmd/readit/main.go +416 -0
  31. package/go/go.mod +20 -0
  32. package/go/go.sum +41 -0
  33. package/go/internal/server/anchor.go +302 -0
  34. package/go/internal/server/anchor_test.go +111 -0
  35. package/go/internal/server/comments.go +390 -0
  36. package/go/internal/server/documents.go +113 -0
  37. package/go/internal/server/embed.go +17 -0
  38. package/go/internal/server/headings.go +33 -0
  39. package/go/internal/server/headings_test.go +75 -0
  40. package/go/internal/server/htmltext.go +123 -0
  41. package/go/internal/server/markdown.go +157 -0
  42. package/go/internal/server/markdown_bench_test.go +42 -0
  43. package/go/internal/server/markdown_test.go +79 -0
  44. package/go/internal/server/server.go +453 -0
  45. package/go/internal/server/server_bench_test.go +122 -0
  46. package/go/internal/server/settings.go +110 -0
  47. package/go/internal/server/sse.go +140 -0
  48. package/go/internal/server/storage.go +275 -0
  49. package/go/internal/server/storage_test.go +118 -0
  50. package/go/internal/server/template.go +66 -0
  51. package/go/internal/server/types.go +101 -0
  52. package/go/internal/server/watcher.go +74 -0
  53. package/index.html +4 -14
  54. package/nvim-readit/lua/readit/health.lua +64 -0
  55. package/nvim-readit/lua/readit/init.lua +463 -0
  56. package/nvim-readit/plugin/readit.lua +19 -0
  57. package/package.json +24 -41
  58. package/playwright.config.ts +12 -0
  59. package/shell/_readit +158 -0
  60. package/shell/readit.zsh +87 -0
  61. package/src/App.svelte +881 -0
  62. package/src/{cli/index.ts → cli.ts} +216 -70
  63. package/src/components/ActionsMenu.svelte +95 -0
  64. package/src/components/CommentBadge.svelte +67 -0
  65. package/src/components/CommentErrorBanner.svelte +33 -0
  66. package/src/components/CommentInput.svelte +75 -0
  67. package/src/components/CommentListItem.svelte +95 -0
  68. package/src/components/CommentManager.svelte +129 -0
  69. package/src/components/CommentNav.svelte +109 -0
  70. package/src/components/DocumentViewer.svelte +218 -0
  71. package/src/components/FloatingComment.svelte +107 -0
  72. package/src/components/Header.svelte +76 -0
  73. package/src/components/InlineEditor.svelte +72 -0
  74. package/src/components/MarginNote.svelte +167 -0
  75. package/src/components/MarginNotesContainer.svelte +33 -0
  76. package/src/components/RawModal.svelte +126 -0
  77. package/src/components/ReanchorConfirm.svelte +30 -0
  78. package/src/components/SettingsModal.svelte +220 -0
  79. package/src/components/ShortcutCapture.svelte +82 -0
  80. package/src/components/ShortcutList.svelte +145 -0
  81. package/src/components/TabBar.svelte +52 -0
  82. package/src/components/TableOfContents.svelte +125 -0
  83. package/src/components/ui/ActionLink.svelte +40 -0
  84. package/src/components/ui/Button.svelte +53 -0
  85. package/src/components/ui/Dialog.svelte +97 -0
  86. package/src/components/ui/DropdownMenu.svelte +85 -0
  87. package/src/components/ui/DropdownMenuItem.svelte +38 -0
  88. package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
  89. package/src/components/ui/Text.svelte +42 -0
  90. package/src/env.d.ts +6 -0
  91. package/src/index.css +36 -166
  92. package/src/lib/__fixtures__/bench-data.ts +1 -54
  93. package/src/lib/anchor.bench.ts +47 -68
  94. package/src/lib/anchor.test.ts +5 -9
  95. package/src/lib/anchor.ts +9 -93
  96. package/src/lib/comment-storage.bench.ts +6 -20
  97. package/src/lib/comment-storage.test.ts +45 -37
  98. package/src/lib/comment-storage.ts +23 -64
  99. package/src/lib/export.bench.ts +9 -23
  100. package/src/lib/export.ts +7 -14
  101. package/src/lib/headings.test.ts +103 -0
  102. package/src/lib/headings.ts +44 -0
  103. package/src/lib/highlight/core.test.ts +1 -6
  104. package/src/lib/highlight/dom.ts +53 -280
  105. package/src/lib/highlight/highlight-registry.ts +221 -0
  106. package/src/lib/highlight/highlight.bench.ts +92 -0
  107. package/src/lib/highlight/highlighter.ts +122 -302
  108. package/src/lib/highlight/{core.ts → resolver.ts} +3 -19
  109. package/src/lib/highlight/types.ts +0 -40
  110. package/src/lib/html-text.test.ts +162 -0
  111. package/src/lib/html-text.ts +161 -0
  112. package/src/lib/i18n/en.ts +13 -36
  113. package/src/lib/i18n/ja.ts +14 -37
  114. package/src/lib/i18n/types.ts +13 -36
  115. package/src/lib/margin-layout.bench.ts +48 -15
  116. package/src/lib/margin-layout.ts +2 -31
  117. package/src/lib/markdown-renderer.test.ts +154 -0
  118. package/src/lib/markdown-renderer.ts +177 -0
  119. package/src/lib/mermaid-config.ts +38 -0
  120. package/src/lib/mermaid-renderer.ts +162 -0
  121. package/src/lib/mermaid-worker.ts +60 -0
  122. package/src/lib/positions.ts +157 -0
  123. package/src/lib/shortcut-registry.ts +138 -103
  124. package/src/lib/utils.ts +2 -48
  125. package/src/main.ts +16 -0
  126. package/src/schema.ts +92 -0
  127. package/src/{server/index.ts → server.ts} +427 -163
  128. package/src/stores/app.svelte.ts +231 -0
  129. package/src/stores/locale.svelte.ts +46 -0
  130. package/src/stores/settings.svelte.ts +90 -0
  131. package/src/stores/shortcuts.svelte.ts +104 -0
  132. package/src/stores/ui.svelte.ts +12 -0
  133. package/src/template.ts +104 -0
  134. package/src/test-setup.ts +47 -0
  135. package/svelte.config.js +5 -0
  136. package/tsconfig.json +2 -2
  137. package/vite.config.ts +31 -3
  138. package/vscode-readit/.mcp.json +7 -0
  139. package/vscode-readit/.vscodeignore +7 -0
  140. package/vscode-readit/bun.lock +78 -0
  141. package/vscode-readit/icon.svg +10 -0
  142. package/vscode-readit/package.json +110 -0
  143. package/vscode-readit/src/extension.ts +117 -0
  144. package/vscode-readit/src/server-manager.ts +272 -0
  145. package/vscode-readit/src/webview-provider.ts +204 -0
  146. package/vscode-readit/tsconfig.json +20 -0
  147. package/e2e/fixtures/sample.html +0 -13
  148. package/src/App.tsx +0 -416
  149. package/src/components/ActionsMenu.tsx +0 -112
  150. package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
  151. package/src/components/DocumentViewer/DocumentViewer.tsx +0 -259
  152. package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
  153. package/src/components/DocumentViewer/InlineCode.tsx +0 -60
  154. package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -137
  155. package/src/components/DocumentViewer/index.ts +0 -1
  156. package/src/components/FloatingTOC.tsx +0 -61
  157. package/src/components/Header.tsx +0 -65
  158. package/src/components/InlineEditor.tsx +0 -74
  159. package/src/components/MarginNote.tsx +0 -207
  160. package/src/components/MarginNotes.tsx +0 -50
  161. package/src/components/RawModal.tsx +0 -143
  162. package/src/components/ReanchorConfirm.tsx +0 -36
  163. package/src/components/SettingsModal.tsx +0 -310
  164. package/src/components/ShortcutCapture.tsx +0 -48
  165. package/src/components/ShortcutList.tsx +0 -198
  166. package/src/components/TabBar.tsx +0 -60
  167. package/src/components/TableOfContents.tsx +0 -108
  168. package/src/components/comments/CommentBadge.tsx +0 -49
  169. package/src/components/comments/CommentInput.tsx +0 -114
  170. package/src/components/comments/CommentListItem.tsx +0 -92
  171. package/src/components/comments/CommentManager.tsx +0 -113
  172. package/src/components/comments/CommentMinimap.tsx +0 -62
  173. package/src/components/comments/CommentNav.tsx +0 -109
  174. package/src/components/ui/ActionBar.tsx +0 -16
  175. package/src/components/ui/ActionLink.tsx +0 -32
  176. package/src/components/ui/Button.tsx +0 -55
  177. package/src/components/ui/Dialog.tsx +0 -156
  178. package/src/components/ui/DropdownMenu.tsx +0 -114
  179. package/src/components/ui/SeparatorDot.tsx +0 -9
  180. package/src/components/ui/Text.tsx +0 -54
  181. package/src/contexts/CommentContext.tsx +0 -229
  182. package/src/contexts/LayoutContext.tsx +0 -88
  183. package/src/contexts/LocaleContext.tsx +0 -35
  184. package/src/hooks/useClickOutside.ts +0 -35
  185. package/src/hooks/useClipboard.ts +0 -82
  186. package/src/hooks/useCommentNavigation.ts +0 -130
  187. package/src/hooks/useComments.ts +0 -323
  188. package/src/hooks/useDocument.ts +0 -156
  189. package/src/hooks/useEditorScheme.ts +0 -51
  190. package/src/hooks/useFontPreference.ts +0 -59
  191. package/src/hooks/useHeadings.test.ts +0 -159
  192. package/src/hooks/useHeadings.ts +0 -129
  193. package/src/hooks/useKeybindings.ts +0 -108
  194. package/src/hooks/useKeyboardShortcuts.ts +0 -63
  195. package/src/hooks/useLayoutMode.ts +0 -44
  196. package/src/hooks/useLocalePreference.ts +0 -42
  197. package/src/hooks/useReanchorMode.ts +0 -33
  198. package/src/hooks/useScrollMetrics.ts +0 -56
  199. package/src/hooks/useScrollSpy.ts +0 -81
  200. package/src/hooks/useTextSelection.ts +0 -123
  201. package/src/hooks/useThemePreference.ts +0 -66
  202. package/src/lib/context.bench.ts +0 -41
  203. package/src/lib/context.test.ts +0 -224
  204. package/src/lib/context.ts +0 -193
  205. package/src/lib/editor-links.ts +0 -59
  206. package/src/lib/highlight/colors.ts +0 -37
  207. package/src/lib/highlight/index.ts +0 -23
  208. package/src/lib/highlight/script-builder.ts +0 -485
  209. package/src/lib/html-processor.test.tsx +0 -170
  210. package/src/lib/html-processor.tsx +0 -95
  211. package/src/lib/i18n/completeness.test.ts +0 -51
  212. package/src/lib/i18n/translations.test.ts +0 -39
  213. package/src/lib/layout-constants.ts +0 -12
  214. package/src/lib/scroll.test.ts +0 -118
  215. package/src/lib/scroll.ts +0 -47
  216. package/src/lib/shortcut-registry.test.ts +0 -173
  217. package/src/lib/utils.test.ts +0 -110
  218. package/src/main.tsx +0 -13
  219. package/src/store/index.test.ts +0 -242
  220. package/src/store/index.ts +0 -254
  221. package/src/types/index.ts +0 -127
@@ -1,224 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { extractContext, formatForLLM, stripHtmlTags } from "./context";
3
-
4
- describe("stripHtmlTags", () => {
5
- it("removes simple HTML tags", () => {
6
- expect(stripHtmlTags("<p>Hello</p>")).toBe("Hello");
7
- expect(stripHtmlTags("<div><span>Nested</span></div>")).toBe("Nested");
8
- });
9
-
10
- it("removes script and style content entirely", () => {
11
- expect(stripHtmlTags('<script>alert("xss")</script>text')).toBe("text");
12
- expect(stripHtmlTags("<style>.foo { color: red }</style>text")).toBe(
13
- "text",
14
- );
15
- });
16
-
17
- it("decodes common named entities", () => {
18
- expect(stripHtmlTags("&lt;tag&gt;")).toBe("<tag>");
19
- expect(stripHtmlTags("&amp;&nbsp;&quot;&#39;")).toBe("& \"'");
20
- });
21
-
22
- it("decodes numeric entities (decimal)", () => {
23
- expect(stripHtmlTags("&#65;&#66;&#67;")).toBe("ABC");
24
- });
25
-
26
- it("decodes numeric entities (hex)", () => {
27
- expect(stripHtmlTags("&#x41;&#x42;&#x43;")).toBe("ABC");
28
- expect(stripHtmlTags("&#X41;&#X42;&#X43;")).toBe("ABC"); // case-insensitive
29
- });
30
-
31
- it("handles mixed content", () => {
32
- const html = "<p>Hello &amp; <strong>World</strong>!</p>";
33
- expect(stripHtmlTags(html)).toBe("Hello & World!");
34
- });
35
-
36
- it("preserves plain text", () => {
37
- expect(stripHtmlTags("plain text")).toBe("plain text");
38
- });
39
- });
40
-
41
- describe("extractContext", () => {
42
- it("extracts single-line selection with markers", () => {
43
- const content = "line1\nline2\nline3\nline4\nline5";
44
- // "line2" starts at offset 6, ends at 11
45
- const result = extractContext({ content, startOffset: 6, endOffset: 11 });
46
-
47
- expect(result.startLine).toBe(2);
48
- expect(result.endLine).toBe(2);
49
- // Should have >>> and <<< markers around "line2"
50
- expect(result.lines.some((l) => l.includes(">>> line2 <<<"))).toBe(true);
51
- });
52
-
53
- it("includes context lines before and after", () => {
54
- const content = "line1\nline2\nline3\nline4\nline5";
55
- // Select "line3" at offset 12
56
- const result = extractContext({
57
- content,
58
- startOffset: 12,
59
- endOffset: 17,
60
- contextLines: 2,
61
- });
62
-
63
- expect(result.startLine).toBe(3);
64
- expect(result.endLine).toBe(3);
65
- // Should include 2 lines before (line1, line2) and 2 after (line4, line5)
66
- expect(result.lines.length).toBe(5);
67
- });
68
-
69
- it("handles multi-line selection", () => {
70
- const content = "line1\nline2\nline3\nline4\nline5";
71
- // Select from "line2" to "line3" (offset 6 to 17)
72
- const result = extractContext({ content, startOffset: 6, endOffset: 17 });
73
-
74
- expect(result.startLine).toBe(2);
75
- expect(result.endLine).toBe(3);
76
- // Start line should have >>> marker
77
- expect(result.lines.some((l) => l.includes(">>>"))).toBe(true);
78
- // End line should have <<< marker
79
- expect(result.lines.some((l) => l.includes("<<<"))).toBe(true);
80
- });
81
-
82
- it("handles selection at start of document", () => {
83
- const content = "line1\nline2\nline3";
84
- // Select "line1" at start
85
- const result = extractContext({ content, startOffset: 0, endOffset: 5 });
86
-
87
- expect(result.startLine).toBe(1);
88
- expect(result.endLine).toBe(1);
89
- expect(result.lines[0]).toContain(">>> line1 <<<");
90
- });
91
-
92
- it("handles selection at end of document", () => {
93
- const content = "line1\nline2\nline3";
94
- // Select "line3" (offset 12 to 17)
95
- const result = extractContext({ content, startOffset: 12, endOffset: 17 });
96
-
97
- expect(result.startLine).toBe(3);
98
- expect(result.endLine).toBe(3);
99
- expect(result.lines.some((l) => l.includes(">>> line3 <<<"))).toBe(true);
100
- });
101
-
102
- it("truncates very long lines", () => {
103
- const longLine = "a".repeat(250);
104
- const content = `short\n${longLine}\nshort`;
105
- // Select part of the long line
106
- const result = extractContext({ content, startOffset: 6, endOffset: 256 });
107
-
108
- // Long line should be truncated with ...
109
- const longLineOutput = result.lines.find((l) => l.length > 100);
110
- expect(longLineOutput?.endsWith("...")).toBe(true);
111
- });
112
-
113
- it("handles HTML content by stripping tags", () => {
114
- const html = "<p>paragraph</p>\n<div>div content</div>";
115
- // After stripping: "paragraph\ndiv content"
116
- // Select "paragraph" at offset 0-9
117
- const result = extractContext({
118
- content: html,
119
- startOffset: 0,
120
- endOffset: 9,
121
- });
122
-
123
- expect(result.lines.some((l) => l.includes(">>> paragraph <<<"))).toBe(
124
- true,
125
- );
126
- });
127
-
128
- it("normalizes CRLF to LF", () => {
129
- const content = "line1\r\nline2\r\nline3";
130
- // After normalization: "line1\nline2\nline3"
131
- // Select "line2" at offset 6
132
- const result = extractContext({ content, startOffset: 6, endOffset: 11 });
133
-
134
- expect(result.startLine).toBe(2);
135
- expect(result.lines.some((l) => l.includes(">>> line2 <<<"))).toBe(true);
136
- });
137
-
138
- it("limits context lines to document bounds", () => {
139
- const content = "only\ntwo\nlines";
140
- // Select middle line with 5 context lines requested
141
- const result = extractContext({
142
- content,
143
- startOffset: 5,
144
- endOffset: 8,
145
- contextLines: 5,
146
- });
147
-
148
- // Should not go beyond document bounds
149
- expect(result.lines.length).toBeLessThanOrEqual(3);
150
- });
151
-
152
- it("truncates very long selections with ellipsis", () => {
153
- // Create content with more than MAX_SELECTION_LINES (10) lines
154
- const lines = Array.from({ length: 15 }, (_, i) => `line${i + 1}`);
155
- const content = lines.join("\n");
156
- // Select all content
157
- const result = extractContext({
158
- content,
159
- startOffset: 0,
160
- endOffset: content.length,
161
- contextLines: 0,
162
- });
163
-
164
- // Should include ... for truncated middle
165
- expect(result.lines.some((l) => l === "...")).toBe(true);
166
- });
167
- });
168
-
169
- describe("formatForLLM", () => {
170
- it("formats context with header and line range", () => {
171
- const context = {
172
- lines: ["before", ">>> selected <<<", "after"],
173
- startLine: 5,
174
- endLine: 5,
175
- };
176
-
177
- const result = formatForLLM({ context, fileName: "test.md" });
178
-
179
- expect(result).toContain("# From: test.md");
180
- expect(result).toContain("Lines 5-5:");
181
- expect(result).toContain("---");
182
- expect(result).toContain(">>> selected <<<");
183
- });
184
-
185
- it("includes optional comment", () => {
186
- const context = {
187
- lines: ["text"],
188
- startLine: 1,
189
- endLine: 1,
190
- };
191
-
192
- const result = formatForLLM({
193
- context,
194
- fileName: "test.md",
195
- comment: "This needs review",
196
- });
197
-
198
- expect(result).toContain("Comment: This needs review");
199
- });
200
-
201
- it("omits comment section when not provided", () => {
202
- const context = {
203
- lines: ["text"],
204
- startLine: 1,
205
- endLine: 1,
206
- };
207
-
208
- const result = formatForLLM({ context, fileName: "test.md" });
209
-
210
- expect(result).not.toContain("Comment:");
211
- });
212
-
213
- it("formats multi-line range correctly", () => {
214
- const context = {
215
- lines: [">>> start", "middle", "end <<<"],
216
- startLine: 10,
217
- endLine: 12,
218
- };
219
-
220
- const result = formatForLLM({ context, fileName: "doc.html" });
221
-
222
- expect(result).toContain("Lines 10-12:");
223
- });
224
- });
@@ -1,193 +0,0 @@
1
- /**
2
- * Context extraction and formatting utilities for LLM clipboard copy.
3
- */
4
-
5
- interface ContextResult {
6
- lines: string[]; // Lines with >>> markers inserted
7
- startLine: number; // 1-based line number
8
- endLine: number; // 1-based line number
9
- }
10
-
11
- interface FormatOptions {
12
- context: ContextResult;
13
- fileName: string;
14
- comment?: string;
15
- }
16
-
17
- /**
18
- * Parameters for context extraction.
19
- * Using object destructuring per style guide §3.5 for clarity.
20
- */
21
- export interface ExtractContextParams {
22
- content: string;
23
- startOffset: number;
24
- endOffset: number;
25
- contextLines?: number;
26
- }
27
-
28
- const DEFAULT_CONTEXT_LINES = 2;
29
- const MAX_SELECTION_LINES = 10;
30
- const MAX_LINE_LENGTH = 200;
31
-
32
- /**
33
- * Strip HTML tags to get plain text matching TreeWalker offset calculation.
34
- */
35
- export function stripHtmlTags(html: string): string {
36
- // Remove script/style content entirely
37
- let text = html.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, "");
38
- // Remove all HTML tags
39
- text = text.replace(/<[^>]+>/g, "");
40
- // Decode common named entities
41
- text = text.replace(/&nbsp;/g, " ");
42
- text = text.replace(/&lt;/g, "<");
43
- text = text.replace(/&gt;/g, ">");
44
- text = text.replace(/&amp;/g, "&");
45
- text = text.replace(/&quot;/g, '"');
46
- text = text.replace(/&#39;/g, "'");
47
- // Decode numeric entities (decimal and hex)
48
- text = text.replace(/&#(\d+);/g, (_, code) =>
49
- String.fromCharCode(Number.parseInt(code, 10)),
50
- );
51
- text = text.replace(/&#x([0-9a-f]+);/gi, (_, code) =>
52
- String.fromCharCode(Number.parseInt(code, 16)),
53
- );
54
- return text;
55
- }
56
-
57
- /**
58
- * Detect if content is HTML based on presence of HTML tags.
59
- */
60
- function isHtml(content: string): boolean {
61
- return /<[a-z][\s\S]*>/i.test(content);
62
- }
63
-
64
- /**
65
- * Truncate a line if it exceeds max length.
66
- */
67
- function truncateLine(
68
- line: string,
69
- maxLength: number = MAX_LINE_LENGTH,
70
- ): string {
71
- if (line.length <= maxLength) return line;
72
- return `${line.slice(0, maxLength - 3)}...`;
73
- }
74
-
75
- /**
76
- * Extract context around a selection using character offsets.
77
- * Returns lines with >>> and <<< markers inserted at selection boundaries.
78
- */
79
- export function extractContext({
80
- content,
81
- startOffset,
82
- endOffset,
83
- contextLines = DEFAULT_CONTEXT_LINES,
84
- }: ExtractContextParams): ContextResult {
85
- // For HTML, strip tags to match offset calculation
86
- const textContent = isHtml(content) ? stripHtmlTags(content) : content;
87
- // Normalize CRLF to LF for consistent offset calculation
88
- const normalizedContent = textContent.replace(/\r\n/g, "\n");
89
-
90
- const lines = normalizedContent.split("\n");
91
- let currentOffset = 0;
92
- let startLineIndex = -1;
93
- let endLineIndex = -1;
94
- let startCharInLine = 0;
95
- let endCharInLine = 0;
96
-
97
- // Find lines containing the selection
98
- for (let i = 0; i < lines.length; i++) {
99
- const lineLength = lines[i].length;
100
- const lineEnd = currentOffset + lineLength;
101
-
102
- if (startLineIndex === -1 && lineEnd >= startOffset) {
103
- startLineIndex = i;
104
- startCharInLine = startOffset - currentOffset;
105
- }
106
-
107
- if (lineEnd >= endOffset) {
108
- endLineIndex = i;
109
- endCharInLine = endOffset - currentOffset;
110
- break;
111
- }
112
-
113
- currentOffset += lineLength + 1; // +1 for newline
114
- }
115
-
116
- // Handle edge case: couldn't find lines
117
- if (startLineIndex === -1) startLineIndex = 0;
118
- if (endLineIndex === -1) endLineIndex = lines.length - 1;
119
-
120
- // Calculate context range
121
- const contextStart = Math.max(0, startLineIndex - contextLines);
122
- const contextEnd = Math.min(lines.length - 1, endLineIndex + contextLines);
123
-
124
- // Build output lines with markers
125
- const outputLines: string[] = [];
126
- const selectionSpan = endLineIndex - startLineIndex + 1;
127
- const shouldTruncateMiddle = selectionSpan > MAX_SELECTION_LINES;
128
-
129
- for (let i = contextStart; i <= contextEnd; i++) {
130
- let line = lines[i];
131
-
132
- // Handle truncation for very long selections
133
- if (shouldTruncateMiddle) {
134
- const showStart = startLineIndex + 2;
135
- const showEnd = endLineIndex - 2;
136
-
137
- if (i > showStart && i < showEnd) {
138
- if (i === showStart + 1) {
139
- outputLines.push("...");
140
- }
141
- continue;
142
- }
143
- }
144
-
145
- // Insert markers for selection boundaries
146
- if (i === startLineIndex && i === endLineIndex) {
147
- // Single line selection
148
- const before = line.slice(0, startCharInLine);
149
- const selected = line.slice(startCharInLine, endCharInLine);
150
- const after = line.slice(endCharInLine);
151
- line = `${before}>>> ${selected} <<<${after}`;
152
- } else if (i === startLineIndex) {
153
- // Start of multi-line selection
154
- const before = line.slice(0, startCharInLine);
155
- const selected = line.slice(startCharInLine);
156
- line = `${before}>>> ${selected}`;
157
- } else if (i === endLineIndex) {
158
- // End of multi-line selection
159
- const selected = line.slice(0, endCharInLine);
160
- const after = line.slice(endCharInLine);
161
- line = `${selected} <<<${after}`;
162
- }
163
-
164
- outputLines.push(truncateLine(line));
165
- }
166
-
167
- return {
168
- lines: outputLines,
169
- startLine: startLineIndex + 1, // 1-based
170
- endLine: endLineIndex + 1, // 1-based
171
- };
172
- }
173
-
174
- /**
175
- * Format extracted context for LLM clipboard copy.
176
- */
177
- export function formatForLLM({
178
- context,
179
- fileName,
180
- comment,
181
- }: FormatOptions): string {
182
- const header = `# From: ${fileName}`;
183
- const lineRange = `Lines ${context.startLine}-${context.endLine}:`;
184
- const body = ["---", ...context.lines, "---"].join("\n");
185
-
186
- const parts = [header, "", lineRange, body];
187
-
188
- if (comment) {
189
- parts.push("", `Comment: ${comment}`);
190
- }
191
-
192
- return parts.join("\n");
193
- }
@@ -1,59 +0,0 @@
1
- import { type EditorScheme, EditorSchemes } from "../types";
2
-
3
- // Known source file extensions — kept broad to cover common languages
4
- const EXTENSIONS =
5
- "ts|tsx|js|jsx|mjs|cjs|json|md|mdx|css|scss|html|htm|py|rs|go|rb|java|kt|yml|yaml|toml|sh|bash|zsh|sql|graphql|gql|vue|svelte|astro|c|cpp|h|hpp|cs|swift|zig|lua|ex|exs|erl|hrl|elm|clj|cljs|ml|mli|fs|fsx|r|jl|dart|tf|hcl|proto|xml|svg";
6
-
7
- // Match file paths with known extensions and optional :line[:col] suffix
8
- // Requires at least one `/` to avoid matching bare filenames like "README.md"
9
- const FILE_PATH_RE = new RegExp(
10
- `^(?:\\.\\.\\/|\\.\\/)?((?:[\\w@.-]+\\/)+[\\w.-]+\\.(?:${EXTENSIONS}))(?::(\\d+)(?::(\\d+))?)?$`,
11
- );
12
-
13
- export interface FilePathMatch {
14
- path: string;
15
- line?: number;
16
- col?: number;
17
- }
18
-
19
- export function parseFilePath(text: string): FilePathMatch | undefined {
20
- const match = FILE_PATH_RE.exec(text.trim());
21
- if (!match) return undefined;
22
-
23
- return {
24
- path: match[1],
25
- line: match[2] ? Number.parseInt(match[2], 10) : undefined,
26
- col: match[3] ? Number.parseInt(match[3], 10) : undefined,
27
- };
28
- }
29
-
30
- export function buildEditorUri(
31
- scheme: EditorScheme,
32
- absolutePath: string,
33
- line?: number,
34
- col?: number,
35
- ): string {
36
- if (scheme === EditorSchemes.NONE) return "";
37
-
38
- // vscode://file/path:line:col
39
- let uri = `${scheme}://file/${absolutePath}`;
40
- if (line !== undefined) {
41
- uri += `:${line}`;
42
- if (col !== undefined) {
43
- uri += `:${col}`;
44
- }
45
- }
46
- return uri;
47
- }
48
-
49
- export function resolveAbsolutePath(
50
- relativePath: string,
51
- workingDirectory: string,
52
- ): string {
53
- if (relativePath.startsWith("/")) return relativePath;
54
-
55
- const base = workingDirectory.endsWith("/")
56
- ? workingDirectory
57
- : `${workingDirectory}/`;
58
- return `${base}${relativePath}`;
59
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * Color palette for comment highlights.
3
- * Colors are assigned by document position (top-to-bottom).
4
- */
5
-
6
- export const COMMENT_COLORS = [
7
- {
8
- name: "amber",
9
- bg: "rgba(245, 222, 160, 0.5)",
10
- bgFocused: "rgba(228, 195, 110, 0.65)",
11
- border: "#c9a84a",
12
- text: "#8b6914",
13
- },
14
- {
15
- name: "blue",
16
- bg: "rgba(168, 196, 228, 0.5)",
17
- bgFocused: "rgba(130, 168, 210, 0.65)",
18
- border: "#5b7fa8",
19
- text: "#3d5f8a",
20
- },
21
- {
22
- name: "green",
23
- bg: "rgba(170, 210, 170, 0.5)",
24
- bgFocused: "rgba(130, 185, 135, 0.65)",
25
- border: "#5a9a62",
26
- text: "#3d6e45",
27
- },
28
- {
29
- name: "rose",
30
- bg: "rgba(225, 180, 185, 0.5)",
31
- bgFocused: "rgba(205, 145, 155, 0.65)",
32
- border: "#b86b78",
33
- text: "#8a4a55",
34
- },
35
- ] as const;
36
-
37
- export type CommentColor = (typeof COMMENT_COLORS)[number];
@@ -1,23 +0,0 @@
1
- // Highlighter (unified adapter)
2
-
3
- export type { CommentColor } from "./colors";
4
- // Colors
5
- export { COMMENT_COLORS } from "./colors";
6
- export type {
7
- Highlighter,
8
- HighlighterOptions,
9
- HoverHandler,
10
- PositionChangeHandler,
11
- SelectionHandler,
12
- } from "./highlighter";
13
- export { createHighlighter } from "./highlighter";
14
-
15
- // Script builder (needed by IframeContainer)
16
- export { buildIframeScript } from "./script-builder";
17
-
18
- // Types (public API)
19
- export type {
20
- HighlightComment,
21
- HighlightPositions,
22
- HighlightStyle,
23
- } from "./types";