@wonderwhy-er/desktop-commander 0.2.38 → 0.2.40

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 (263) hide show
  1. package/README.md +53 -2
  2. package/dist/handlers/filesystem-handlers.d.ts +5 -0
  3. package/dist/handlers/filesystem-handlers.js +14 -2
  4. package/dist/remote-device/desktop-commander-integration.js +1 -1
  5. package/dist/search-manager.js +31 -38
  6. package/dist/server.js +9 -4
  7. package/dist/terminal-manager.js +4 -2
  8. package/dist/tools/edit.js +34 -1
  9. package/dist/tools/filesystem.js +91 -3
  10. package/dist/tools/improved-process-tools.js +2 -1
  11. package/dist/ui/config-editor/config-editor-runtime.js +65 -14096
  12. package/dist/ui/config-editor/styles.css +2 -1
  13. package/dist/ui/file-preview/preview-runtime.js +435 -26533
  14. package/dist/ui/file-preview/shared/preview-file-types.d.ts +1 -1
  15. package/dist/ui/file-preview/src/app.d.ts +1 -5
  16. package/dist/ui/file-preview/src/app.js +384 -534
  17. package/dist/ui/file-preview/src/components/markdown-renderer.js +47 -9
  18. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  19. package/dist/ui/file-preview/src/directory-controller.js +233 -0
  20. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  21. package/dist/ui/file-preview/src/document-layout.js +109 -0
  22. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  23. package/dist/ui/file-preview/src/document-outline.js +97 -0
  24. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  25. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  26. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  27. package/dist/ui/file-preview/src/file-type-handlers.js +98 -0
  28. package/dist/ui/file-preview/src/host/external-actions.d.ts +19 -0
  29. package/dist/ui/file-preview/src/host/external-actions.js +94 -0
  30. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  31. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  32. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  33. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  34. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  35. package/dist/ui/file-preview/src/markdown/controller.js +1040 -0
  36. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  37. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  38. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  39. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  40. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  41. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  42. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  43. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  44. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  45. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  46. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  47. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  48. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  49. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  50. package/dist/ui/file-preview/src/model.d.ts +35 -0
  51. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  52. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  53. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  54. package/dist/ui/file-preview/src/path-utils.js +64 -0
  55. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  56. package/dist/ui/file-preview/src/payload-utils.js +94 -0
  57. package/dist/ui/file-preview/styles.css +1066 -233
  58. package/dist/ui/shared/widget-state.d.ts +6 -1
  59. package/dist/ui/shared/widget-state.js +102 -4
  60. package/dist/utils/capture.js +1 -1
  61. package/dist/utils/files/base.d.ts +2 -0
  62. package/dist/utils/open-browser.js +1 -1
  63. package/dist/utils/toolHistory.d.ts +13 -0
  64. package/dist/utils/toolHistory.js +65 -0
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/package.json +12 -1
  68. package/dist/data/spec-kit-prompts.json +0 -123
  69. package/dist/handlers/macos-control-handlers.d.ts +0 -16
  70. package/dist/handlers/macos-control-handlers.js +0 -81
  71. package/dist/handlers/node-handlers.d.ts +0 -6
  72. package/dist/handlers/node-handlers.js +0 -73
  73. package/dist/handlers/test-crash-handler.d.ts +0 -11
  74. package/dist/handlers/test-crash-handler.js +0 -26
  75. package/dist/http-index.d.ts +0 -45
  76. package/dist/http-index.js +0 -51
  77. package/dist/http-server-auto-tunnel.js +0 -667
  78. package/dist/http-server-named-tunnel.d.ts +0 -2
  79. package/dist/http-server-named-tunnel.js +0 -167
  80. package/dist/http-server-tunnel.d.ts +0 -2
  81. package/dist/http-server-tunnel.js +0 -111
  82. package/dist/http-server.d.ts +0 -2
  83. package/dist/http-server.js +0 -270
  84. package/dist/index-oauth.d.ts +0 -2
  85. package/dist/index-oauth.js +0 -201
  86. package/dist/lib.d.ts +0 -10
  87. package/dist/lib.js +0 -10
  88. package/dist/oauth/auth-middleware.d.ts +0 -20
  89. package/dist/oauth/auth-middleware.js +0 -62
  90. package/dist/oauth/index.d.ts +0 -3
  91. package/dist/oauth/index.js +0 -3
  92. package/dist/oauth/oauth-manager.d.ts +0 -80
  93. package/dist/oauth/oauth-manager.js +0 -179
  94. package/dist/oauth/oauth-routes.d.ts +0 -3
  95. package/dist/oauth/oauth-routes.js +0 -377
  96. package/dist/oauth/provider.d.ts +0 -22
  97. package/dist/oauth/provider.js +0 -124
  98. package/dist/oauth/server.d.ts +0 -18
  99. package/dist/oauth/server.js +0 -160
  100. package/dist/oauth/types.d.ts +0 -54
  101. package/dist/oauth/types.js +0 -2
  102. package/dist/remote-device/templates/auth-success.d.ts +0 -1
  103. package/dist/remote-device/templates/auth-success.js +0 -30
  104. package/dist/setup.log +0 -275
  105. package/dist/test-docx.d.ts +0 -1
  106. package/dist/test-setup.js +0 -14
  107. package/dist/tools/docx/builders/html-builder.d.ts +0 -17
  108. package/dist/tools/docx/builders/html-builder.js +0 -92
  109. package/dist/tools/docx/builders/image.d.ts +0 -14
  110. package/dist/tools/docx/builders/image.js +0 -84
  111. package/dist/tools/docx/builders/index.d.ts +0 -11
  112. package/dist/tools/docx/builders/index.js +0 -11
  113. package/dist/tools/docx/builders/markdown-builder.d.ts +0 -2
  114. package/dist/tools/docx/builders/markdown-builder.js +0 -260
  115. package/dist/tools/docx/builders/paragraph.d.ts +0 -12
  116. package/dist/tools/docx/builders/paragraph.js +0 -29
  117. package/dist/tools/docx/builders/table.d.ts +0 -10
  118. package/dist/tools/docx/builders/table.js +0 -138
  119. package/dist/tools/docx/builders/utils.d.ts +0 -5
  120. package/dist/tools/docx/builders/utils.js +0 -18
  121. package/dist/tools/docx/constants.d.ts +0 -32
  122. package/dist/tools/docx/constants.js +0 -61
  123. package/dist/tools/docx/converters/markdown-to-html.d.ts +0 -17
  124. package/dist/tools/docx/converters/markdown-to-html.js +0 -111
  125. package/dist/tools/docx/create.d.ts +0 -21
  126. package/dist/tools/docx/create.js +0 -386
  127. package/dist/tools/docx/dom.d.ts +0 -139
  128. package/dist/tools/docx/dom.js +0 -448
  129. package/dist/tools/docx/errors.d.ts +0 -28
  130. package/dist/tools/docx/errors.js +0 -48
  131. package/dist/tools/docx/extractors/images.d.ts +0 -14
  132. package/dist/tools/docx/extractors/images.js +0 -40
  133. package/dist/tools/docx/extractors/metadata.d.ts +0 -14
  134. package/dist/tools/docx/extractors/metadata.js +0 -64
  135. package/dist/tools/docx/extractors/sections.d.ts +0 -14
  136. package/dist/tools/docx/extractors/sections.js +0 -61
  137. package/dist/tools/docx/html.d.ts +0 -17
  138. package/dist/tools/docx/html.js +0 -111
  139. package/dist/tools/docx/index.d.ts +0 -10
  140. package/dist/tools/docx/index.js +0 -10
  141. package/dist/tools/docx/markdown.d.ts +0 -84
  142. package/dist/tools/docx/markdown.js +0 -507
  143. package/dist/tools/docx/modify.d.ts +0 -28
  144. package/dist/tools/docx/modify.js +0 -271
  145. package/dist/tools/docx/operations/handlers/index.d.ts +0 -39
  146. package/dist/tools/docx/operations/handlers/index.js +0 -152
  147. package/dist/tools/docx/operations/html-manipulator.d.ts +0 -24
  148. package/dist/tools/docx/operations/html-manipulator.js +0 -352
  149. package/dist/tools/docx/operations/index.d.ts +0 -14
  150. package/dist/tools/docx/operations/index.js +0 -61
  151. package/dist/tools/docx/operations/operation-handlers.d.ts +0 -3
  152. package/dist/tools/docx/operations/operation-handlers.js +0 -67
  153. package/dist/tools/docx/operations/preprocessor.d.ts +0 -14
  154. package/dist/tools/docx/operations/preprocessor.js +0 -44
  155. package/dist/tools/docx/operations/xml-replacer.d.ts +0 -9
  156. package/dist/tools/docx/operations/xml-replacer.js +0 -35
  157. package/dist/tools/docx/operations.d.ts +0 -13
  158. package/dist/tools/docx/operations.js +0 -13
  159. package/dist/tools/docx/ops/delete-paragraph-at-body-index.d.ts +0 -11
  160. package/dist/tools/docx/ops/delete-paragraph-at-body-index.js +0 -23
  161. package/dist/tools/docx/ops/header-replace-text-exact.d.ts +0 -13
  162. package/dist/tools/docx/ops/header-replace-text-exact.js +0 -55
  163. package/dist/tools/docx/ops/index.d.ts +0 -17
  164. package/dist/tools/docx/ops/index.js +0 -70
  165. package/dist/tools/docx/ops/insert-image-after-text.d.ts +0 -24
  166. package/dist/tools/docx/ops/insert-image-after-text.js +0 -128
  167. package/dist/tools/docx/ops/insert-paragraph-after-text.d.ts +0 -12
  168. package/dist/tools/docx/ops/insert-paragraph-after-text.js +0 -74
  169. package/dist/tools/docx/ops/insert-table-after-text.d.ts +0 -19
  170. package/dist/tools/docx/ops/insert-table-after-text.js +0 -57
  171. package/dist/tools/docx/ops/replace-hyperlink-url.d.ts +0 -12
  172. package/dist/tools/docx/ops/replace-hyperlink-url.js +0 -37
  173. package/dist/tools/docx/ops/replace-paragraph-at-body-index.d.ts +0 -9
  174. package/dist/tools/docx/ops/replace-paragraph-at-body-index.js +0 -25
  175. package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +0 -21
  176. package/dist/tools/docx/ops/replace-paragraph-text-exact.js +0 -36
  177. package/dist/tools/docx/ops/replace-table-cell-text.d.ts +0 -25
  178. package/dist/tools/docx/ops/replace-table-cell-text.js +0 -85
  179. package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +0 -9
  180. package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +0 -24
  181. package/dist/tools/docx/ops/set-color-for-style.d.ts +0 -13
  182. package/dist/tools/docx/ops/set-color-for-style.js +0 -31
  183. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.d.ts +0 -8
  184. package/dist/tools/docx/ops/set-paragraph-style-at-body-index.js +0 -57
  185. package/dist/tools/docx/ops/table-set-cell-text.d.ts +0 -9
  186. package/dist/tools/docx/ops/table-set-cell-text.js +0 -40
  187. package/dist/tools/docx/parsers/image-extractor.d.ts +0 -18
  188. package/dist/tools/docx/parsers/image-extractor.js +0 -61
  189. package/dist/tools/docx/parsers/index.d.ts +0 -9
  190. package/dist/tools/docx/parsers/index.js +0 -9
  191. package/dist/tools/docx/parsers/paragraph-parser.d.ts +0 -2
  192. package/dist/tools/docx/parsers/paragraph-parser.js +0 -88
  193. package/dist/tools/docx/parsers/table-parser.d.ts +0 -9
  194. package/dist/tools/docx/parsers/table-parser.js +0 -72
  195. package/dist/tools/docx/parsers/xml-parser.d.ts +0 -25
  196. package/dist/tools/docx/parsers/xml-parser.js +0 -71
  197. package/dist/tools/docx/parsers/zip-reader.d.ts +0 -23
  198. package/dist/tools/docx/parsers/zip-reader.js +0 -52
  199. package/dist/tools/docx/read.d.ts +0 -27
  200. package/dist/tools/docx/read.js +0 -308
  201. package/dist/tools/docx/relationships.d.ts +0 -22
  202. package/dist/tools/docx/relationships.js +0 -76
  203. package/dist/tools/docx/structure.d.ts +0 -25
  204. package/dist/tools/docx/structure.js +0 -102
  205. package/dist/tools/docx/styled-html-parser.d.ts +0 -23
  206. package/dist/tools/docx/styled-html-parser.js +0 -1262
  207. package/dist/tools/docx/types.d.ts +0 -213
  208. package/dist/tools/docx/types.js +0 -5
  209. package/dist/tools/docx/utils/escaping.d.ts +0 -13
  210. package/dist/tools/docx/utils/escaping.js +0 -26
  211. package/dist/tools/docx/utils/images.d.ts +0 -9
  212. package/dist/tools/docx/utils/images.js +0 -26
  213. package/dist/tools/docx/utils/index.d.ts +0 -12
  214. package/dist/tools/docx/utils/index.js +0 -17
  215. package/dist/tools/docx/utils/markdown.d.ts +0 -13
  216. package/dist/tools/docx/utils/markdown.js +0 -32
  217. package/dist/tools/docx/utils/paths.d.ts +0 -15
  218. package/dist/tools/docx/utils/paths.js +0 -27
  219. package/dist/tools/docx/utils/versioning.d.ts +0 -25
  220. package/dist/tools/docx/utils/versioning.js +0 -55
  221. package/dist/tools/docx/utils.d.ts +0 -101
  222. package/dist/tools/docx/utils.js +0 -299
  223. package/dist/tools/docx/validate.d.ts +0 -33
  224. package/dist/tools/docx/validate.js +0 -49
  225. package/dist/tools/docx/validators.d.ts +0 -13
  226. package/dist/tools/docx/validators.js +0 -40
  227. package/dist/tools/docx/write.d.ts +0 -17
  228. package/dist/tools/docx/write.js +0 -88
  229. package/dist/tools/docx/xml-view-test.d.ts +0 -1
  230. package/dist/tools/docx/xml-view-test.js +0 -63
  231. package/dist/tools/docx/xml-view.d.ts +0 -56
  232. package/dist/tools/docx/xml-view.js +0 -169
  233. package/dist/tools/docx/zip.d.ts +0 -21
  234. package/dist/tools/docx/zip.js +0 -35
  235. package/dist/tools/macos-control/ax-adapter.d.ts +0 -55
  236. package/dist/tools/macos-control/ax-adapter.js +0 -438
  237. package/dist/tools/macos-control/cdp-adapter.d.ts +0 -23
  238. package/dist/tools/macos-control/cdp-adapter.js +0 -402
  239. package/dist/tools/macos-control/orchestrator.d.ts +0 -77
  240. package/dist/tools/macos-control/orchestrator.js +0 -136
  241. package/dist/tools/macos-control/role-aliases.d.ts +0 -5
  242. package/dist/tools/macos-control/role-aliases.js +0 -34
  243. package/dist/tools/macos-control/types.d.ts +0 -129
  244. package/dist/tools/macos-control/types.js +0 -1
  245. package/dist/tools/pdf-processor.d.ts +0 -1
  246. package/dist/tools/pdf-processor.js +0 -3
  247. package/dist/tools/search.d.ts +0 -32
  248. package/dist/tools/search.js +0 -202
  249. package/dist/ui/file-preview/src/components/toolbar.d.ts +0 -6
  250. package/dist/ui/file-preview/src/components/toolbar.js +0 -75
  251. package/dist/ui/shared/host-lifecycle.d.ts +0 -16
  252. package/dist/ui/shared/host-lifecycle.js +0 -35
  253. package/dist/ui/shared/rpc-client.d.ts +0 -14
  254. package/dist/ui/shared/rpc-client.js +0 -72
  255. package/dist/ui/shared/theme-adaptation.d.ts +0 -10
  256. package/dist/ui/shared/theme-adaptation.js +0 -118
  257. package/dist/ui/shared/tool-header.d.ts +0 -9
  258. package/dist/ui/shared/tool-header.js +0 -25
  259. package/dist/utils/crash-logger.d.ts +0 -18
  260. package/dist/utils/crash-logger.js +0 -44
  261. package/dist/utils/dedent.d.ts +0 -8
  262. package/dist/utils/dedent.js +0 -38
  263. /package/dist/{http-server-auto-tunnel.d.ts → ui/file-preview/src/model.js} +0 -0
@@ -1,21 +1,59 @@
1
1
  /**
2
2
  * Markdown rendering pipeline for preview mode. It configures markdown-it and highlighting so markdown content is rendered consistently with code block support.
3
3
  */
4
- // markdown-it is intentionally typed locally here to avoid maintaining global ambient module declarations.
5
- // @ts-expect-error markdown-it does not provide local TypeScript typings in this setup.
6
- import MarkdownIt from 'markdown-it';
7
4
  import { highlightSource } from './highlighting.js';
8
- const MarkdownItCtor = MarkdownIt;
9
- const markdown = new MarkdownItCtor({
10
- html: false,
11
- linkify: true,
12
- typographer: false,
5
+ import { createMarkdownIt, prepareMarkdownSource, readHeadingProjection } from '../markdown/parser.js';
6
+ import { createSlugTracker } from '../markdown/slugify.js';
7
+ const markdown = createMarkdownIt({
13
8
  highlight(code, language) {
14
9
  const normalizedLanguage = (language || 'text').toLowerCase();
15
10
  const highlighted = highlightSource(code, normalizedLanguage);
16
11
  return `<pre class="code-viewer"><code class="hljs language-${normalizedLanguage}">${highlighted}</code></pre>`;
17
12
  }
18
13
  });
14
+ const renderHeadingOpen = markdown.renderer.rules.heading_open;
15
+ markdown.renderer.rules.heading_open = (...args) => {
16
+ const tokens = args[0];
17
+ const index = args[1];
18
+ const options = args[2];
19
+ const environment = args[3] ?? {};
20
+ const self = args[4];
21
+ const nextSlug = typeof environment.nextSlug === 'function'
22
+ ? environment.nextSlug
23
+ : createSlugTracker();
24
+ environment.nextSlug = nextSlug;
25
+ const heading = readHeadingProjection(tokens, index, nextSlug);
26
+ const token = tokens[index];
27
+ if (heading) {
28
+ token.attrSet?.('id', heading.id);
29
+ token.attrSet?.('data-heading-id', heading.id);
30
+ }
31
+ if (typeof renderHeadingOpen === 'function') {
32
+ return renderHeadingOpen(...args);
33
+ }
34
+ return self.renderToken(tokens, index, options);
35
+ };
36
+ const renderLinkOpen = markdown.renderer.rules.link_open;
37
+ markdown.renderer.rules.link_open = (...args) => {
38
+ const tokens = args[0];
39
+ const index = args[1];
40
+ const options = args[2];
41
+ const self = args[4];
42
+ const token = tokens[index];
43
+ token.attrSet?.('data-markdown-link', 'true');
44
+ const title = token.attrGet?.('title');
45
+ if (title?.startsWith('mcp-wiki:')) {
46
+ const rawWikiLink = decodeURIComponent(title.slice('mcp-wiki:'.length));
47
+ token.attrSet?.('data-wiki-link', rawWikiLink);
48
+ if (Array.isArray(token.attrs)) {
49
+ token.attrs = token.attrs.filter(([name]) => name !== 'title');
50
+ }
51
+ }
52
+ if (typeof renderLinkOpen === 'function') {
53
+ return renderLinkOpen(...args);
54
+ }
55
+ return self.renderToken(tokens, index, options);
56
+ };
19
57
  export function renderMarkdown(content) {
20
- return markdown.render(content);
58
+ return markdown.render(prepareMarkdownSource(content), { nextSlug: createSlugTracker() });
21
59
  }
@@ -0,0 +1,8 @@
1
+ import type { RenderBodyResult, RenderPayload } from './model.js';
2
+ export declare function renderDirectoryBody(content: string, rootPath: string): RenderBodyResult;
3
+ export declare function attachDirectoryHandlers(options: {
4
+ container: HTMLElement;
5
+ callTool?: (name: string, args: Record<string, unknown>) => Promise<unknown | undefined>;
6
+ buildOpenInFolderCommand: (filePath: string) => string | undefined;
7
+ onOpenPayload: (payload: RenderPayload) => void;
8
+ }): void;
@@ -0,0 +1,233 @@
1
+ import { escapeHtml } from './components/highlighting.js';
2
+ import { buildRenderPayload, extractToolText } from './payload-utils.js';
3
+ function parseDirectoryEntries(content) {
4
+ const lines = content.split('\n');
5
+ const hintLines = [];
6
+ const entryLines = [];
7
+ for (const line of lines) {
8
+ if (/^\[(DIR|FILE|DENIED|WARNING)\]/.test(line.trim())) {
9
+ entryLines.push(line.trim());
10
+ }
11
+ else if (entryLines.length === 0) {
12
+ hintLines.push(line);
13
+ }
14
+ }
15
+ const flat = [];
16
+ for (const line of entryLines) {
17
+ if (line.startsWith('[WARNING]')) {
18
+ const warnBody = line.replace(/^\[WARNING\]\s*/, '');
19
+ const colonIdx = warnBody.indexOf(':');
20
+ const dirName = colonIdx >= 0 ? warnBody.slice(0, colonIdx).trim() : '';
21
+ const msg = colonIdx >= 0 ? warnBody.slice(colonIdx + 1).trim() : warnBody;
22
+ const parts = dirName.replace(/\\/g, '/').split('/').filter(Boolean);
23
+ flat.push({
24
+ name: dirName,
25
+ fullPath: dirName,
26
+ isDir: false,
27
+ isDenied: false,
28
+ isWarning: true,
29
+ warningText: msg,
30
+ depth: parts.length,
31
+ });
32
+ continue;
33
+ }
34
+ const isDir = line.startsWith('[DIR]');
35
+ const isDenied = line.startsWith('[DENIED]');
36
+ const name = line.replace(/^\[(DIR|FILE|DENIED)\]\s*/, '');
37
+ const parts = name.replace(/\\/g, '/').split('/');
38
+ flat.push({
39
+ name,
40
+ fullPath: name,
41
+ isDir,
42
+ isDenied,
43
+ isWarning: false,
44
+ warningText: '',
45
+ depth: parts.length - 1,
46
+ });
47
+ }
48
+ const root = [];
49
+ const stack = [root];
50
+ for (const item of flat) {
51
+ const baseName = item.fullPath.replace(/\\/g, '/').split('/').pop() ?? item.fullPath;
52
+ const entry = {
53
+ name: baseName,
54
+ isDir: item.isDir,
55
+ isDenied: item.isDenied,
56
+ isWarning: item.isWarning,
57
+ warningText: item.warningText,
58
+ children: [],
59
+ relativePath: item.fullPath,
60
+ };
61
+ while (stack.length > item.depth + 1) {
62
+ stack.pop();
63
+ }
64
+ const parent = stack[stack.length - 1];
65
+ parent.push(entry);
66
+ if (item.isDir) {
67
+ stack.push(entry.children);
68
+ }
69
+ }
70
+ return { hint: hintLines.join('\n').trim(), entries: root };
71
+ }
72
+ let dirEntryIdCounter = 0;
73
+ function renderDirTree(entries, rootPath) {
74
+ if (entries.length === 0) {
75
+ return '<div class="dir-tree"><span class="dir-empty">Empty directory</span></div>';
76
+ }
77
+ function renderEntries(items) {
78
+ return items.map((item) => {
79
+ const id = `de-${dirEntryIdCounter++}`;
80
+ const fullPath = `${rootPath}/${item.relativePath.replace(/\\/g, '/')}`;
81
+ const escapedPath = escapeHtml(fullPath);
82
+ if (item.isWarning) {
83
+ return `<div class="dir-entry"><button class="dir-row dir-row-warning dir-load-more" data-loadpath="${escapedPath}"><span class="dir-warning-icon">⚠️</span> <span class="dir-warning-text">${escapeHtml(item.warningText)} — click to load all</span></button></div>`;
84
+ }
85
+ if (item.isDenied) {
86
+ return `<div class="dir-entry"><span class="dir-icon">🚫</span> <span class="dir-name-denied">${escapeHtml(item.name)}</span></div>`;
87
+ }
88
+ if (item.isDir) {
89
+ const hasChildren = item.children.length > 0;
90
+ const chevron = `<span class="dir-chevron${hasChildren ? ' expanded' : ''}">${hasChildren ? '▼' : '▶'}</span>`;
91
+ const openButton = `<button class="dir-open-btn" data-openpath="${escapedPath}" title="Open in Finder">📂</button>`;
92
+ const childrenHtml = hasChildren ? `<div class="dir-children" id="${id}-ch">${renderEntries(item.children)}</div>` : '';
93
+ return `<div class="dir-entry-group" id="${id}"><div class="dir-row dir-row-folder" data-path="${escapedPath}" data-eid="${id}" data-loaded="${hasChildren}">${chevron} <span class="dir-icon">📁</span> <span class="dir-name">${escapeHtml(item.name)}</span>${openButton}</div>${childrenHtml}</div>`;
94
+ }
95
+ return `<div class="dir-entry"><div class="dir-row dir-row-file" data-path="${escapedPath}"><span class="file-icon">📄</span> <span class="file-name">${escapeHtml(item.name)}</span></div></div>`;
96
+ }).join('');
97
+ }
98
+ return `<div class="dir-tree">${renderEntries(entries)}</div>`;
99
+ }
100
+ export function renderDirectoryBody(content, rootPath) {
101
+ dirEntryIdCounter = 0;
102
+ const { hint, entries } = parseDirectoryEntries(content);
103
+ return {
104
+ notice: hint || undefined,
105
+ html: `<div class="panel-content directory-content">${renderDirTree(entries, rootPath)}</div>`,
106
+ };
107
+ }
108
+ export function attachDirectoryHandlers(options) {
109
+ const tree = options.container.querySelector('.dir-tree');
110
+ if (!tree) {
111
+ return;
112
+ }
113
+ tree.addEventListener('click', async (event) => {
114
+ const openBtn = event.target.closest('.dir-open-btn');
115
+ if (openBtn) {
116
+ event.stopPropagation();
117
+ const openPath = openBtn.dataset.openpath;
118
+ if (!openPath) {
119
+ return;
120
+ }
121
+ const command = options.buildOpenInFolderCommand(openPath);
122
+ if (command) {
123
+ try {
124
+ await options.callTool?.('start_process', { command, timeout_ms: 12000 });
125
+ }
126
+ catch {
127
+ // Keep UI stable if opening folder fails.
128
+ }
129
+ }
130
+ return;
131
+ }
132
+ const loadMoreBtn = event.target.closest('.dir-load-more');
133
+ if (loadMoreBtn) {
134
+ event.stopPropagation();
135
+ const loadPath = loadMoreBtn.dataset.loadpath;
136
+ if (!loadPath) {
137
+ return;
138
+ }
139
+ loadMoreBtn.querySelector('.dir-warning-text').textContent = 'Loading…';
140
+ loadMoreBtn.disabled = true;
141
+ try {
142
+ const result = await options.callTool?.('list_directory', { path: loadPath, depth: 1 });
143
+ const text = extractToolText(result) ?? '';
144
+ if (text) {
145
+ const parsed = parseDirectoryEntries(text);
146
+ const html = renderDirTree(parsed.entries, loadPath);
147
+ const parentChildren = loadMoreBtn.closest('.dir-children');
148
+ if (parentChildren) {
149
+ const temp = document.createElement('div');
150
+ temp.innerHTML = html;
151
+ const inner = temp.querySelector('.dir-tree');
152
+ parentChildren.innerHTML = inner ? inner.innerHTML : '';
153
+ }
154
+ }
155
+ }
156
+ catch {
157
+ loadMoreBtn.querySelector('.dir-warning-text').textContent = 'Failed to load';
158
+ loadMoreBtn.disabled = false;
159
+ }
160
+ return;
161
+ }
162
+ const target = event.target.closest('.dir-row');
163
+ if (!target) {
164
+ return;
165
+ }
166
+ const fullPath = target.dataset.path;
167
+ if (!fullPath) {
168
+ return;
169
+ }
170
+ if (target.classList.contains('dir-row-folder')) {
171
+ const entryId = target.dataset.eid;
172
+ if (!entryId) {
173
+ return;
174
+ }
175
+ const childrenEl = document.getElementById(`${entryId}-ch`);
176
+ const chevron = target.querySelector('.dir-chevron');
177
+ if (childrenEl) {
178
+ const hidden = childrenEl.classList.toggle('dir-collapsed');
179
+ chevron?.classList.toggle('expanded', !hidden);
180
+ if (chevron)
181
+ chevron.textContent = hidden ? '▶' : '▼';
182
+ return;
183
+ }
184
+ if (target.dataset.loaded === 'true') {
185
+ return;
186
+ }
187
+ if (chevron)
188
+ chevron.textContent = '⏳';
189
+ try {
190
+ const result = await options.callTool?.('list_directory', { path: fullPath, depth: 2 });
191
+ const text = extractToolText(result) ?? '';
192
+ if (text) {
193
+ target.dataset.loaded = 'true';
194
+ const parsed = parseDirectoryEntries(text);
195
+ const html = renderDirTree(parsed.entries, fullPath);
196
+ const wrapper = document.createElement('div');
197
+ wrapper.className = 'dir-children';
198
+ wrapper.id = `${entryId}-ch`;
199
+ const temp = document.createElement('div');
200
+ temp.innerHTML = html;
201
+ const inner = temp.querySelector('.dir-tree');
202
+ wrapper.innerHTML = inner ? inner.innerHTML : '<span class="dir-empty">Empty</span>';
203
+ target.parentElement?.appendChild(wrapper);
204
+ chevron?.classList.add('expanded');
205
+ if (chevron)
206
+ chevron.textContent = '▼';
207
+ }
208
+ }
209
+ catch {
210
+ if (chevron)
211
+ chevron.textContent = '⚠';
212
+ }
213
+ return;
214
+ }
215
+ if (target.classList.contains('dir-row-file')) {
216
+ target.classList.add('dir-loading');
217
+ try {
218
+ const result = await options.callTool?.('read_file', { path: fullPath });
219
+ if (!result || typeof result !== 'object' || result === null) {
220
+ return;
221
+ }
222
+ const structuredContent = result.structuredContent;
223
+ if (structuredContent && typeof structuredContent === 'object') {
224
+ const text = extractToolText(result) ?? '';
225
+ options.onOpenPayload(buildRenderPayload(structuredContent, text));
226
+ }
227
+ }
228
+ catch {
229
+ target.classList.remove('dir-loading');
230
+ }
231
+ }
232
+ });
233
+ }
@@ -0,0 +1,20 @@
1
+ import type { FileTypeCapabilities, MarkdownWorkspaceState, RenderBodyResult, RenderPayload } from './model.js';
2
+ export declare function buildDocumentLayout(options: {
3
+ payload: RenderPayload;
4
+ body: RenderBodyResult;
5
+ capabilities: FileTypeCapabilities;
6
+ fileExtension: string;
7
+ htmlMode: 'rendered' | 'source';
8
+ currentDisplayMode: string | null;
9
+ isExpanded: boolean;
10
+ hideSummaryRow: boolean;
11
+ markdownWorkspace?: MarkdownWorkspaceState;
12
+ canGoFullscreen: boolean;
13
+ isMarkdownUndoAvailable: boolean;
14
+ defaultMarkdownEditorName?: string;
15
+ markdownEditorAppIcon: string;
16
+ hasDirectoryBackButton: boolean;
17
+ }): {
18
+ html: string;
19
+ effectiveExpanded: boolean;
20
+ };
@@ -0,0 +1,109 @@
1
+ import { renderCompactRow } from '../../shared/compact-row.js';
2
+ import { escapeHtml } from '../../shared/escape-html.js';
3
+ import { parseReadRange, stripReadStatusLine } from './document-workspace.js';
4
+ import { renderMarkdownCopyButton, renderMarkdownModeToggle } from './markdown/editor.js';
5
+ import { buildBreadcrumb, countContentLines } from './payload-utils.js';
6
+ function renderCopyIcon() {
7
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
8
+ }
9
+ function renderFolderIcon() {
10
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
11
+ }
12
+ function renderUndoIcon() {
13
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h11a5 5 0 1 1 0 10h-1"/></svg>';
14
+ }
15
+ function renderExpandIcon() {
16
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>';
17
+ }
18
+ function renderMarkdownSaveStatus(workspace) {
19
+ if (workspace.fileDeleted) {
20
+ return '<span class="panel-save-status panel-save-status--saved">File deleted</span>';
21
+ }
22
+ if (workspace.saveIndicator !== 'saved') {
23
+ return '';
24
+ }
25
+ const variant = workspace.saving ? 'saving' : 'saved';
26
+ return `<span class="panel-save-status panel-save-status--${variant}">Saved</span>`;
27
+ }
28
+ export function buildDocumentLayout(options) {
29
+ const range = parseReadRange(options.payload.content);
30
+ const notice = options.body.notice ? `<div class="notice">${options.body.notice}</div>` : '';
31
+ const breadcrumb = buildBreadcrumb(options.payload.filePath);
32
+ const lineCount = range ? range.toLine - range.fromLine + 1 : countContentLines(options.payload.content);
33
+ const fileTypeLabel = options.payload.fileType === 'markdown' ? 'MARKDOWN'
34
+ : options.payload.fileType === 'html' ? 'HTML'
35
+ : options.payload.fileType === 'image' ? 'IMAGE'
36
+ : options.payload.fileType === 'directory' ? 'DIRECTORY'
37
+ : options.fileExtension !== 'none' ? options.fileExtension.toUpperCase()
38
+ : 'TEXT';
39
+ const compactLabel = range?.isPartial
40
+ ? `View lines ${range.fromLine}–${range.toLine}`
41
+ : options.payload.fileType === 'directory' ? 'View directory'
42
+ : 'View file';
43
+ let footerLabel = range?.isPartial
44
+ ? `${fileTypeLabel} • LINES ${range.fromLine}–${range.toLine} OF ${range.totalLines}`
45
+ : `${fileTypeLabel} • ${lineCount} LINE${lineCount !== 1 ? 'S' : ''}`;
46
+ if (options.markdownWorkspace) {
47
+ const source = stripReadStatusLine(options.markdownWorkspace.draftContent);
48
+ const markdownWordCount = source.trim().split(/\s+/).filter(Boolean).length;
49
+ const markdownLineCount = countContentLines(source);
50
+ footerLabel = `${fileTypeLabel} • EDIT MODE • ${markdownLineCount} LINES • ${markdownWordCount} WORDS`;
51
+ }
52
+ const isFullscreen = options.currentDisplayMode === 'fullscreen';
53
+ const htmlToggle = options.payload.fileType === 'html'
54
+ ? `<button class="panel-action" id="toggle-html-mode">${options.htmlMode === 'rendered' ? 'Source' : 'Rendered'}</button>`
55
+ : '';
56
+ const backButton = options.hasDirectoryBackButton && options.payload.fileType !== 'directory'
57
+ ? '<button class="panel-action dir-back-btn" id="dir-back" title="Back to directory">← Back</button>'
58
+ : '';
59
+ const isMarkdown = options.payload.fileType === 'markdown';
60
+ const isMarkdownEdit = isMarkdown && !!options.markdownWorkspace;
61
+ const revertDisabled = isMarkdownEdit && (options.markdownWorkspace.fileDeleted || options.markdownWorkspace.loadingDocument || !options.isMarkdownUndoAvailable);
62
+ const fileDeleted = isMarkdownEdit && options.markdownWorkspace.fileDeleted;
63
+ const hasMissingBefore = range?.isPartial && range.fromLine > 1;
64
+ const hasMissingAfter = range?.isPartial && range.toLine < range.totalLines && (range.totalLines - range.toLine) > 1;
65
+ const loadBeforeBanner = hasMissingBefore
66
+ ? `<button class="load-lines-banner" id="load-before">↑ Load lines 1–${range.fromLine - 1}</button>`
67
+ : '';
68
+ const loadAfterBanner = hasMissingAfter
69
+ ? `<button class="load-lines-banner" id="load-after">↓ Load lines ${range.toLine + 1}–${range.totalLines}</button>`
70
+ : '';
71
+ const effectiveExpanded = options.isExpanded || isFullscreen;
72
+ const canOpenInFolder = options.capabilities.canOpenInFolder;
73
+ const canCopy = options.capabilities.canCopy;
74
+ return {
75
+ effectiveExpanded,
76
+ html: `
77
+ <main id="tool-shell" class="shell tool-shell ${effectiveExpanded ? 'expanded' : 'collapsed'}${options.hideSummaryRow ? ' host-framed' : ''}${isFullscreen ? ' fullscreen' : ''}">
78
+ ${isFullscreen ? '' : renderCompactRow({ id: 'compact-toggle', label: compactLabel, filename: options.payload.fileName, variant: 'ready', expandable: true, expanded: options.isExpanded, interactive: true })}
79
+ <section class="panel">
80
+ <div class="panel-topbar">
81
+ ${backButton}
82
+ ${options.hideSummaryRow ? '' : `<span class="panel-breadcrumb" title="${escapeHtml(options.payload.filePath)}">${breadcrumb}</span>`}
83
+ <span class="panel-topbar-actions">
84
+ ${isMarkdownEdit ? renderMarkdownSaveStatus(options.markdownWorkspace) : ''}
85
+ ${htmlToggle}
86
+ ${isMarkdownEdit && isFullscreen ? renderMarkdownModeToggle(options.markdownWorkspace.editorView) : ''}
87
+ ${isMarkdown && !isFullscreen && options.canGoFullscreen ? `<button class="panel-action" id="expand-fullscreen" title="Expand" aria-label="Expand">${renderExpandIcon()}</button>` : ''}
88
+ ${isMarkdownEdit ? `<button class="panel-action" id="revert-markdown" ${isFullscreen ? '' : 'title="Undo" aria-label="Undo" '}${revertDisabled ? 'disabled' : ''}>${renderUndoIcon()}${isFullscreen ? ' Undo' : ''}</button>` : ''}
89
+ ${isMarkdownEdit && isFullscreen ? renderMarkdownCopyButton() : ''}
90
+ ${isMarkdown && !isFullscreen ? `<button class="panel-action" id="copy-active-markdown" title="Copy" aria-label="Copy">${renderCopyIcon()}</button>` : ''}
91
+ ${canCopy && options.capabilities.supportsPreview && !isMarkdown ? `<button class="panel-action" id="copy-source" title="Copy source" aria-label="Copy source">${renderCopyIcon()}</button>` : ''}
92
+ ${canOpenInFolder && isMarkdownEdit && isFullscreen ? `<button class="panel-action" id="open-in-editor" ${fileDeleted ? 'disabled' : ''}>${options.markdownEditorAppIcon} Open in ${escapeHtml(options.defaultMarkdownEditorName ?? 'editor')}</button>` : ''}
93
+ ${canOpenInFolder && !(isMarkdownEdit && isFullscreen) ? `<button class="panel-action" id="open-in-folder" title="Open in folder" aria-label="Open in folder" ${isMarkdownEdit && fileDeleted ? 'disabled' : ''}>${renderFolderIcon()}</button>` : ''}
94
+ </span>
95
+ </div>
96
+ ${notice}
97
+ <div class="panel-content-wrapper">
98
+ ${loadBeforeBanner}
99
+ ${options.body.html}
100
+ ${loadAfterBanner}
101
+ </div>
102
+ <div class="panel-footer">
103
+ <span>${footerLabel}</span>
104
+ </div>
105
+ </section>
106
+ </main>
107
+ `,
108
+ };
109
+ }
@@ -0,0 +1,17 @@
1
+ export interface DocumentOutlineItem {
2
+ id: string;
3
+ text: string;
4
+ level: number;
5
+ line?: number;
6
+ }
7
+ export interface DocumentOutlineHandle {
8
+ dispose: () => void;
9
+ refresh: (outline: DocumentOutlineItem[], activeHeadingId?: string | null) => void;
10
+ }
11
+ export declare function renderDocumentOutline(outline: DocumentOutlineItem[], activeHeadingId?: string | null): string;
12
+ export declare function attachDocumentOutline(options: {
13
+ shell: HTMLElement;
14
+ outline: DocumentOutlineItem[];
15
+ scrollContainer: HTMLElement;
16
+ onSelect: (headingId: string) => void;
17
+ }): DocumentOutlineHandle | null;
@@ -0,0 +1,97 @@
1
+ import { escapeHtml } from '../../shared/escape-html.js';
2
+ function setActiveItem(nav, activeId) {
3
+ const buttons = Array.from(nav.querySelectorAll('[data-toc-id]'));
4
+ buttons.forEach((button) => {
5
+ const isActive = button.dataset.tocId === activeId;
6
+ button.classList.toggle('is-active', isActive);
7
+ button.setAttribute('aria-current', isActive ? 'location' : 'false');
8
+ });
9
+ }
10
+ function renderDocumentOutlineItems(outline, activeHeadingId) {
11
+ return outline.map((item) => {
12
+ const activeClass = item.id === activeHeadingId ? ' is-active' : '';
13
+ return `<button class="document-outline-link markdown-toc-link${activeClass}" type="button" data-toc-id="${escapeHtml(item.id)}" data-level="${item.level}" aria-current="${item.id === activeHeadingId ? 'location' : 'false'}">${escapeHtml(item.text)}</button>`;
14
+ }).join('');
15
+ }
16
+ function renderCollapseIcon() {
17
+ return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>';
18
+ }
19
+ function renderExpandIcon() {
20
+ return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>';
21
+ }
22
+ export function renderDocumentOutline(outline, activeHeadingId) {
23
+ if (outline.length === 0) {
24
+ return '';
25
+ }
26
+ return `
27
+ <aside class="document-outline-shell markdown-toc-shell" aria-label="Table of contents">
28
+ <div class="document-outline-header markdown-toc-header">
29
+ <span class="document-outline-title markdown-toc-title">Contents</span>
30
+ <button class="document-outline-toggle" id="toc-toggle" type="button" title="Collapse sidebar" aria-label="Collapse sidebar">${renderCollapseIcon()}</button>
31
+ </div>
32
+ <nav class="document-outline-nav markdown-toc-nav">${renderDocumentOutlineItems(outline, activeHeadingId)}</nav>
33
+ </aside>
34
+ `;
35
+ }
36
+ export function attachDocumentOutline(options) {
37
+ const nav = options.shell.querySelector('.document-outline-nav');
38
+ if (!nav) {
39
+ return null;
40
+ }
41
+ let currentOutline = options.outline;
42
+ const handleClick = (event) => {
43
+ const target = event.target;
44
+ const button = target?.closest('[data-toc-id]');
45
+ const headingId = button?.dataset.tocId;
46
+ if (!headingId) {
47
+ return;
48
+ }
49
+ options.onSelect(headingId);
50
+ setActiveItem(nav, headingId);
51
+ };
52
+ const updateActiveHeading = () => {
53
+ const headings = currentOutline
54
+ .map((item) => {
55
+ const element = document.getElementById(item.id);
56
+ return element ? { item, element } : null;
57
+ })
58
+ .filter((entry) => entry !== null);
59
+ if (headings.length === 0) {
60
+ return;
61
+ }
62
+ const scrollTop = options.scrollContainer.scrollTop;
63
+ const nextActive = headings.reduce((activeId, current) => {
64
+ if (current.element.offsetTop - scrollTop <= 96) {
65
+ return current.item.id;
66
+ }
67
+ return activeId;
68
+ }, headings[0].item.id);
69
+ setActiveItem(nav, nextActive);
70
+ };
71
+ const toggleButton = options.shell.querySelector('#toc-toggle');
72
+ const handleToggle = () => {
73
+ const isCollapsed = options.shell.classList.toggle('markdown-toc-collapsed');
74
+ if (toggleButton) {
75
+ toggleButton.innerHTML = isCollapsed ? renderExpandIcon() : renderCollapseIcon();
76
+ toggleButton.title = isCollapsed ? 'Expand sidebar' : 'Collapse sidebar';
77
+ toggleButton.setAttribute('aria-label', isCollapsed ? 'Expand sidebar' : 'Collapse sidebar');
78
+ }
79
+ };
80
+ toggleButton?.addEventListener('click', handleToggle);
81
+ nav.addEventListener('click', handleClick);
82
+ options.scrollContainer.addEventListener('scroll', updateActiveHeading, { passive: true });
83
+ updateActiveHeading();
84
+ return {
85
+ dispose: () => {
86
+ nav.removeEventListener('click', handleClick);
87
+ options.scrollContainer.removeEventListener('scroll', updateActiveHeading);
88
+ toggleButton?.removeEventListener('click', handleToggle);
89
+ },
90
+ refresh: (outline, activeHeadingId) => {
91
+ currentOutline = outline;
92
+ nav.innerHTML = renderDocumentOutlineItems(currentOutline, activeHeadingId);
93
+ setActiveItem(nav, activeHeadingId ?? null);
94
+ updateActiveHeading();
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,19 @@
1
+ export interface ReadRange {
2
+ fromLine: number;
3
+ toLine: number;
4
+ totalLines: number;
5
+ isPartial: boolean;
6
+ /** 0-based offset for read_file calls. "from start" → 0, "from line N" → N. */
7
+ readOffset: number;
8
+ }
9
+ export declare function stripReadStatusLine(content: string): string;
10
+ export declare function parseReadRange(content: string): ReadRange | undefined;
11
+ export declare function getDocumentFullscreenAvailability(options: {
12
+ availableDisplayModes?: string[];
13
+ }): {
14
+ canFullscreen: true;
15
+ } | {
16
+ canFullscreen: false;
17
+ reason: string;
18
+ };
19
+ export declare function shouldAutoLoadDocumentOnEnterFullscreen(content: string): boolean;
@@ -0,0 +1,33 @@
1
+ export function stripReadStatusLine(content) {
2
+ return content.replace(/^\[Reading [^\]]+\]\r?\n(?:\r?\n)?/, '');
3
+ }
4
+ export function parseReadRange(content) {
5
+ const match = content.match(/^\[Reading (\d+) lines from (?:line )?(\d+|start) \(total: (\d+) lines/);
6
+ if (!match) {
7
+ return undefined;
8
+ }
9
+ const count = Number.parseInt(match[1], 10);
10
+ const isFromStart = match[2] === 'start';
11
+ const readOffset = isFromStart ? 0 : Number.parseInt(match[2], 10);
12
+ const fromLine = isFromStart ? 1 : readOffset;
13
+ const totalLines = Number.parseInt(match[3], 10);
14
+ return {
15
+ fromLine,
16
+ toLine: fromLine + count - 1,
17
+ totalLines,
18
+ isPartial: count < totalLines,
19
+ readOffset,
20
+ };
21
+ }
22
+ export function getDocumentFullscreenAvailability(options) {
23
+ if (!options.availableDisplayModes?.includes('fullscreen')) {
24
+ return {
25
+ canFullscreen: false,
26
+ reason: 'Fullscreen editing is unavailable in this host.',
27
+ };
28
+ }
29
+ return { canFullscreen: true };
30
+ }
31
+ export function shouldAutoLoadDocumentOnEnterFullscreen(content) {
32
+ return parseReadRange(content)?.isPartial === true;
33
+ }
@@ -0,0 +1,10 @@
1
+ import type { FileTypeCapabilities, RenderBodyResult, RenderPayload } from './model.js';
2
+ import type { MarkdownController } from './markdown/controller.js';
3
+ import type { HtmlPreviewMode } from './types.js';
4
+ export declare function getFileTypeCapabilities(payload: RenderPayload): FileTypeCapabilities;
5
+ export declare function renderPayloadBody(options: {
6
+ payload: RenderPayload;
7
+ htmlMode: HtmlPreviewMode;
8
+ startLine: number;
9
+ markdownController: MarkdownController;
10
+ }): RenderBodyResult;