pi-studio 0.5.47 → 0.5.49

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.
package/client/studio.css CHANGED
@@ -898,6 +898,24 @@
898
898
  scroll-margin-top: 24px;
899
899
  }
900
900
 
901
+ .preview-comment-line-block {
902
+ min-height: 1.5em;
903
+ }
904
+
905
+ .preview-code-lines {
906
+ white-space: pre-wrap;
907
+ word-break: break-word;
908
+ overflow-wrap: anywhere;
909
+ }
910
+
911
+ .preview-code-line-content {
912
+ display: block;
913
+ min-height: 1.5em;
914
+ white-space: inherit;
915
+ word-break: inherit;
916
+ overflow-wrap: inherit;
917
+ }
918
+
901
919
  .preview-comment-controls {
902
920
  position: absolute;
903
921
  top: 0;
package/index.ts CHANGED
@@ -10,6 +10,7 @@ import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path
10
10
  import { URL, pathToFileURL } from "node:url";
11
11
  import { WebSocketServer, WebSocket, type RawData } from "ws";
12
12
  import {
13
+ advancePastStudioInlineBacktickSpan,
13
14
  collectStudioInlineAnnotationMarkers,
14
15
  hasStudioMarkdownAnnotationMarkers,
15
16
  isStudioAnnotationWordChar,
@@ -18,6 +19,7 @@ import {
18
19
  replaceStudioInlineAnnotationMarkers,
19
20
  transformStudioMarkdownOutsideFences,
20
21
  } from "./shared/studio-annotation-scanner.js";
22
+ import { stripStudioMarkdownHtmlComments } from "./shared/studio-markdown-html-comments.js";
21
23
  import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
22
24
 
23
25
  type Lens = "writing" | "code";
@@ -2892,114 +2894,6 @@ function normalizeMathDelimiters(markdown: string): string {
2892
2894
  return out.join("\n");
2893
2895
  }
2894
2896
 
2895
- function stripStudioMarkdownHtmlCommentsInSegment(markdown: string): string {
2896
- const source = String(markdown ?? "");
2897
- let out = "";
2898
- let i = 0;
2899
- let codeSpanFenceLength = 0;
2900
- let inHtmlComment = false;
2901
-
2902
- while (i < source.length) {
2903
- if (inHtmlComment) {
2904
- if (source.startsWith("-->", i)) {
2905
- inHtmlComment = false;
2906
- i += 3;
2907
- continue;
2908
- }
2909
- const ch = source[i]!;
2910
- if (ch === "\n" || ch === "\r") out += ch;
2911
- i += 1;
2912
- continue;
2913
- }
2914
-
2915
- if (codeSpanFenceLength > 0) {
2916
- const fence = "`".repeat(codeSpanFenceLength);
2917
- if (source.startsWith(fence, i)) {
2918
- out += fence;
2919
- i += codeSpanFenceLength;
2920
- codeSpanFenceLength = 0;
2921
- continue;
2922
- }
2923
- out += source[i]!;
2924
- i += 1;
2925
- continue;
2926
- }
2927
-
2928
- const backtickMatch = source.slice(i).match(/^`+/);
2929
- if (backtickMatch) {
2930
- const fence = backtickMatch[0]!;
2931
- codeSpanFenceLength = fence.length;
2932
- out += fence;
2933
- i += fence.length;
2934
- continue;
2935
- }
2936
-
2937
- if (source.startsWith("<!--", i)) {
2938
- inHtmlComment = true;
2939
- i += 4;
2940
- continue;
2941
- }
2942
-
2943
- out += source[i]!;
2944
- i += 1;
2945
- }
2946
-
2947
- return out;
2948
- }
2949
-
2950
- function stripStudioMarkdownHtmlComments(markdown: string): string {
2951
- const lines = String(markdown ?? "").split("\n");
2952
- const out: string[] = [];
2953
- let plainBuffer: string[] = [];
2954
- let inFence = false;
2955
- let fenceChar: "`" | "~" | undefined;
2956
- let fenceLength = 0;
2957
-
2958
- const flushPlain = () => {
2959
- if (plainBuffer.length === 0) return;
2960
- out.push(stripStudioMarkdownHtmlCommentsInSegment(plainBuffer.join("\n")));
2961
- plainBuffer = [];
2962
- };
2963
-
2964
- for (const line of lines) {
2965
- const trimmed = line.trimStart();
2966
- const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
2967
-
2968
- if (fenceMatch) {
2969
- const marker = fenceMatch[1]!;
2970
- const markerChar = marker[0] as "`" | "~";
2971
- const markerLength = marker.length;
2972
-
2973
- if (!inFence) {
2974
- flushPlain();
2975
- inFence = true;
2976
- fenceChar = markerChar;
2977
- fenceLength = markerLength;
2978
- out.push(line);
2979
- continue;
2980
- }
2981
-
2982
- if (fenceChar === markerChar && markerLength >= fenceLength) {
2983
- inFence = false;
2984
- fenceChar = undefined;
2985
- fenceLength = 0;
2986
- }
2987
-
2988
- out.push(line);
2989
- continue;
2990
- }
2991
-
2992
- if (inFence) {
2993
- out.push(line);
2994
- } else {
2995
- plainBuffer.push(line);
2996
- }
2997
- }
2998
-
2999
- flushPlain();
3000
- return out.join("\n");
3001
- }
3002
-
3003
2897
  const STUDIO_PREVIEW_PAGE_BREAK_SENTINEL_PREFIX = "PI_STUDIO_PAGE_BREAK__";
3004
2898
 
3005
2899
  function replaceStudioPreviewPageBreakCommands(markdown: string): string {
@@ -3263,6 +3157,26 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
3263
3157
  return undefined;
3264
3158
  }
3265
3159
 
3160
+ function stripStudioMarkdownInlineCodeSpans(markdown: string): string {
3161
+ const source = String(markdown ?? "");
3162
+ let out = "";
3163
+ let index = 0;
3164
+ while (index < source.length) {
3165
+ if (source[index] === "`") {
3166
+ index = advancePastStudioInlineBacktickSpan(source, index);
3167
+ continue;
3168
+ }
3169
+ out += source[index];
3170
+ index += 1;
3171
+ }
3172
+ return out;
3173
+ }
3174
+
3175
+ function isLikelyStandaloneLatexPreview(markdown: string): boolean {
3176
+ const outsideFences = transformStudioMarkdownOutsideFences(markdown, (segment: string) => stripStudioMarkdownInlineCodeSpans(segment));
3177
+ return /\\documentclass\b|\\begin\{document\}/.test(outsideFences);
3178
+ }
3179
+
3266
3180
  function escapeStudioPdfLatexText(text: string): string {
3267
3181
  const normalized = String(text ?? "")
3268
3182
  .replace(/\r\n/g, "\n")
@@ -4043,9 +3957,15 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
4043
3957
  }
4044
3958
 
4045
3959
  function stripMathMlAnnotationTags(html: string): string {
4046
- return html
4047
- .replace(/<annotation-xml\b[\s\S]*?<\/annotation-xml>/gi, "")
4048
- .replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
3960
+ return String(html ?? "").replace(/<math\b([^>]*)>([\s\S]*?)<\/math>/gi, (_match, attrs, inner) => {
3961
+ const texAnnotationMatch = String(inner ?? "").match(/<annotation\b[^>]*encoding="application\/x-tex"[^>]*>([\s\S]*?)<\/annotation>/i);
3962
+ const texSource = texAnnotationMatch ? String(texAnnotationMatch[1] ?? "").trim() : "";
3963
+ const cleanedInner = String(inner ?? "")
3964
+ .replace(/<annotation-xml\b[\s\S]*?<\/annotation-xml>/gi, "")
3965
+ .replace(/<annotation\b[\s\S]*?<\/annotation>/gi, "");
3966
+ const texAttr = texSource ? ` data-tex-source="${escapeStudioHtmlText(texSource)}"` : "";
3967
+ return `<math${attrs}${texAttr}>${cleanedInner}</math>`;
3968
+ });
4049
3969
  }
4050
3970
 
4051
3971
  function normalizeObsidianImages(markdown: string): string {
@@ -7869,8 +7789,17 @@ export default function (pi: ExtensionAPI) {
7869
7789
  parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { resourceDir?: unknown }).resourceDir === "string"
7870
7790
  ? (parsedBody as { resourceDir: string }).resourceDir
7871
7791
  : "";
7792
+ const requestedEditorLanguage =
7793
+ parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { editorLanguage?: unknown }).editorLanguage === "string"
7794
+ ? (parsedBody as { editorLanguage: string }).editorLanguage
7795
+ : "";
7872
7796
  const resourcePath = resolveStudioBaseDir(sourcePath || undefined, userResourceDir || undefined, studioCwd);
7873
- const isLatex = /\\documentclass\b|\\begin\{document\}/.test(markdown);
7797
+ const editorPreviewLanguage = normalizeStudioEditorLanguage(requestedEditorLanguage);
7798
+ const isLatex = editorPreviewLanguage === "latex"
7799
+ || (
7800
+ (editorPreviewLanguage === undefined || editorPreviewLanguage === "markdown")
7801
+ && isLikelyStandaloneLatexPreview(markdown)
7802
+ );
7874
7803
  const html = await renderStudioMarkdownWithPandoc(markdown, isLatex, resourcePath, sourcePath || undefined);
7875
7804
  respondJson(res, 200, { ok: true, html, renderer: "pandoc" });
7876
7805
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.47",
3
+ "version": "0.5.49",
4
4
  "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,111 @@
1
+ export function stripStudioMarkdownHtmlCommentsInSegment(markdown) {
2
+ const source = String(markdown ?? "");
3
+ let out = "";
4
+ let index = 0;
5
+ let codeSpanFenceLength = 0;
6
+ let inHtmlComment = false;
7
+
8
+ while (index < source.length) {
9
+ if (inHtmlComment) {
10
+ if (source.startsWith("-->", index)) {
11
+ inHtmlComment = false;
12
+ index += 3;
13
+ continue;
14
+ }
15
+ const ch = source[index];
16
+ if (ch === "\n" || ch === "\r") out += ch;
17
+ index += 1;
18
+ continue;
19
+ }
20
+
21
+ if (codeSpanFenceLength > 0) {
22
+ const fence = "`".repeat(codeSpanFenceLength);
23
+ if (source.startsWith(fence, index)) {
24
+ out += fence;
25
+ index += codeSpanFenceLength;
26
+ codeSpanFenceLength = 0;
27
+ continue;
28
+ }
29
+ const ch = source[index];
30
+ out += ch;
31
+ index += 1;
32
+ if (ch === "\n" || ch === "\r") {
33
+ codeSpanFenceLength = 0;
34
+ }
35
+ continue;
36
+ }
37
+
38
+ const backtickMatch = source.slice(index).match(/^`+/);
39
+ if (backtickMatch) {
40
+ const fence = backtickMatch[0];
41
+ codeSpanFenceLength = fence.length;
42
+ out += fence;
43
+ index += fence.length;
44
+ continue;
45
+ }
46
+
47
+ if (source.startsWith("<!--", index)) {
48
+ inHtmlComment = true;
49
+ index += 4;
50
+ continue;
51
+ }
52
+
53
+ out += source[index];
54
+ index += 1;
55
+ }
56
+
57
+ return out;
58
+ }
59
+
60
+ export function stripStudioMarkdownHtmlComments(markdown) {
61
+ const lines = String(markdown ?? "").split("\n");
62
+ const out = [];
63
+ let plainBuffer = [];
64
+ let inFence = false;
65
+ let fenceChar;
66
+ let fenceLength = 0;
67
+
68
+ const flushPlain = () => {
69
+ if (plainBuffer.length === 0) return;
70
+ out.push(stripStudioMarkdownHtmlCommentsInSegment(plainBuffer.join("\n")));
71
+ plainBuffer = [];
72
+ };
73
+
74
+ for (const line of lines) {
75
+ const trimmed = line.trimStart();
76
+ const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
77
+
78
+ if (fenceMatch) {
79
+ const marker = fenceMatch[1];
80
+ const markerChar = marker[0];
81
+ const markerLength = marker.length;
82
+
83
+ if (!inFence) {
84
+ flushPlain();
85
+ inFence = true;
86
+ fenceChar = markerChar;
87
+ fenceLength = markerLength;
88
+ out.push(line);
89
+ continue;
90
+ }
91
+
92
+ if (fenceChar === markerChar && markerLength >= fenceLength) {
93
+ inFence = false;
94
+ fenceChar = undefined;
95
+ fenceLength = 0;
96
+ }
97
+
98
+ out.push(line);
99
+ continue;
100
+ }
101
+
102
+ if (inFence) {
103
+ out.push(line);
104
+ } else {
105
+ plainBuffer.push(line);
106
+ }
107
+ }
108
+
109
+ flushPlain();
110
+ return out.join("\n");
111
+ }