pi-studio 0.2.0 → 0.2.2
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/CHANGELOG.md +21 -0
- package/README.md +34 -12
- package/assets/screenshots/dark-code-mode.png +0 -0
- package/assets/screenshots/dark-fenced-code.png +0 -0
- package/assets/screenshots/dark-julia-fenced.png +0 -0
- package/assets/screenshots/dark-math.png +0 -0
- package/assets/screenshots/dark-mermaid.png +0 -0
- package/assets/screenshots/dark-workspace.png +0 -0
- package/assets/screenshots/light-code-mode.png +0 -0
- package/assets/screenshots/light-fenced-code.png +0 -0
- package/assets/screenshots/light-math.png +0 -0
- package/assets/screenshots/light-workspace.png +0 -0
- package/index.ts +205 -4
- package/package.json +1 -1
- package/assets/screenshots/dark-annotation.png +0 -0
- package/assets/screenshots/dark-critique.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `pi-studio` are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.2.2] — 2026-03-02
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Corrected screenshot-to-label mapping in README (files were copied to wrong filenames).
|
|
9
|
+
|
|
10
|
+
## [0.2.1] — 2026-03-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Language-aware syntax highlighting**: selectable `Lang:` dropdown (Markdown, JavaScript, TypeScript, Python, Bash, JSON, Rust, C, C++, Julia, Fortran, R, MATLAB).
|
|
14
|
+
- Language auto-detected from file extension when loading files; manually overridable via dropdown.
|
|
15
|
+
- Full-document code highlighting in editor Raw view when a non-markdown language is selected (reuses fenced-block tokenizer across entire content).
|
|
16
|
+
- Code-aware Preview: when a code language is selected, Preview renders syntax-highlighted `<pre>` instead of sending to pandoc.
|
|
17
|
+
- Language preference persisted to `localStorage` across sessions.
|
|
18
|
+
- New tokenizer patterns for Rust, C/C++, Julia, Fortran, R, and MATLAB (keywords, strings, comments, numbers).
|
|
19
|
+
- Expanded file-accept list for Load file content (`.h`, `.hpp`, `.jl`, `.f90`, `.f95`, `.f03`, `.f`, `.for`, `.r`, `.R`, `.m`, `.lua`).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Renamed "Load file in editor" → "Load file content" (clarifies that file content is copied, not edited in-place).
|
|
23
|
+
- Lang selector visibility: shown when syntax highlight is On (Raw view) or in Preview mode; hidden otherwise.
|
|
24
|
+
- Updated README with comprehensive screenshot gallery (markdown, math, mermaid, code mode, fenced code).
|
|
25
|
+
|
|
5
26
|
## [0.2.0] — 2026-03-02
|
|
6
27
|
|
|
7
28
|
### Added
|
package/README.md
CHANGED
|
@@ -6,21 +6,37 @@ Status: experimental alpha.
|
|
|
6
6
|
|
|
7
7
|
## Screenshots
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Markdown workspace — dark** (syntax-highlighted editor + rendered preview with Julia code block, inline math, blockquotes)
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**Markdown workspace — light**
|
|
14
14
|
|
|
15
|
-

|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**Math rendering — dark** (LaTeX source → MathML: PDEs, matrices, display equations)
|
|
18
18
|
|
|
19
|
-

|
|
20
|
+
|
|
21
|
+
**Math rendering — light**
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
**Mermaid diagrams — dark** (fenced mermaid block → rendered flowchart with theme-aware colors)
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**Code mode — dark** (TypeScript file loaded with language auto-detected from extension)
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
**Code mode — light**
|
|
34
|
+
|
|
35
|
+

|
|
20
36
|
|
|
21
|
-
**
|
|
37
|
+
**Julia fenced code — dark** (token coloring for fenced code blocks inside markdown)
|
|
22
38
|
|
|
23
|
-

|
|
24
40
|
|
|
25
41
|
## Features
|
|
26
42
|
|
|
@@ -31,10 +47,17 @@ Status: experimental alpha.
|
|
|
31
47
|
- Response load helpers:
|
|
32
48
|
- non-critique: **Load response into editor**
|
|
33
49
|
- critique: **Load critique (notes)** / **Load critique (full)**
|
|
34
|
-
- File actions: **Save As…**, **Save file**, **Load file
|
|
35
|
-
- View toggles: `Editor:
|
|
50
|
+
- File actions: **Save As…**, **Save file**, **Load file content**
|
|
51
|
+
- View toggles: `Editor: Raw|Preview`, `Response: Raw|Preview`
|
|
36
52
|
- Preview mode supports MathML equations and Mermaid fenced diagrams
|
|
37
|
-
-
|
|
53
|
+
- **Language-aware syntax highlighting** with selectable language mode:
|
|
54
|
+
- Markdown (default): headings, links, code fences, lists, quotes, inline code
|
|
55
|
+
- Code languages: JavaScript, TypeScript, Python, Bash, JSON, Rust, C, C++, Julia, Fortran, R, MATLAB
|
|
56
|
+
- Keywords, strings, comments, numbers, and variables highlighted using theme syntax color tokens
|
|
57
|
+
- Language auto-detected from file extension on file load; manually selectable via `Lang:` dropdown
|
|
58
|
+
- Applies to both editor Raw view (highlight overlay) and fenced code blocks in markdown
|
|
59
|
+
- Preview mode renders syntax-highlighted code when a non-markdown language is selected
|
|
60
|
+
- Separate syntax highlight toggles for editor and response Raw views, with local preference persistence
|
|
38
61
|
- Theme-aware browser UI based on current pi theme, with refined surface depth and lighter visual chrome
|
|
39
62
|
|
|
40
63
|
## Commands
|
|
@@ -73,7 +96,7 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
73
96
|
|
|
74
97
|
- Local-only server (`127.0.0.1`) with rotating session tokens.
|
|
75
98
|
- One studio request at a time.
|
|
76
|
-
- Pi Studio
|
|
99
|
+
- Pi Studio supports both markdown workflows (model responses, plans, and notes) and code file editing with language-aware syntax highlighting.
|
|
77
100
|
- Studio URLs include a token query parameter; avoid sharing full Studio URLs.
|
|
78
101
|
- Preview panes render markdown via `pandoc` (`gfm+tex_math_dollars` → HTML5 + MathML), including pandoc code syntax highlighting, sanitized in-browser with `dompurify`.
|
|
79
102
|
- Preview markdown/code colors are mapped from active theme markdown (`md*`) and syntax (`syntax*`) tokens for closer terminal-vs-browser parity.
|
|
@@ -82,7 +105,6 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
82
105
|
- Preview rendering normalizes Obsidian wiki-image syntax (`![[path]]`, `![[path|alt]]`) into standard markdown images.
|
|
83
106
|
- Install pandoc for full preview rendering (`brew install pandoc` on macOS).
|
|
84
107
|
- If `pandoc` is unavailable, preview falls back to plain markdown text with an inline warning.
|
|
85
|
-
- Some screenshots may show the Grammarly browser widget. Grammarly is a separate browser extension and is not part of pi-studio, but it works alongside it.
|
|
86
108
|
|
|
87
109
|
## License
|
|
88
110
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.ts
CHANGED
|
@@ -1917,7 +1917,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1917
1917
|
</select>
|
|
1918
1918
|
<button id="saveAsBtn" type="button" title="Save editor text to a new file path.">Save As…</button>
|
|
1919
1919
|
<button id="saveOverBtn" type="button" title="Overwrite current file with editor text." disabled>Save file</button>
|
|
1920
|
-
<label class="file-label" title="Load a local file into editor text.">Load file
|
|
1920
|
+
<label class="file-label" title="Load a local file into editor text.">Load file content<input id="fileInput" type="file" accept=".txt,.md,.markdown,.rst,.adoc,.tex,.json,.js,.ts,.py,.java,.c,.cpp,.h,.hpp,.go,.rs,.rb,.swift,.sh,.html,.css,.xml,.yaml,.yml,.toml,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.lua" /></label>
|
|
1921
1921
|
</div>
|
|
1922
1922
|
</header>
|
|
1923
1923
|
|
|
@@ -1945,6 +1945,21 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
1945
1945
|
<option value="off">Syntax highlight: Off</option>
|
|
1946
1946
|
<option value="on" selected>Syntax highlight: On</option>
|
|
1947
1947
|
</select>
|
|
1948
|
+
<select id="langSelect" aria-label="Highlight language">
|
|
1949
|
+
<option value="markdown" selected>Lang: Markdown</option>
|
|
1950
|
+
<option value="javascript">Lang: JavaScript</option>
|
|
1951
|
+
<option value="typescript">Lang: TypeScript</option>
|
|
1952
|
+
<option value="python">Lang: Python</option>
|
|
1953
|
+
<option value="bash">Lang: Bash</option>
|
|
1954
|
+
<option value="json">Lang: JSON</option>
|
|
1955
|
+
<option value="rust">Lang: Rust</option>
|
|
1956
|
+
<option value="c">Lang: C</option>
|
|
1957
|
+
<option value="cpp">Lang: C++</option>
|
|
1958
|
+
<option value="julia">Lang: Julia</option>
|
|
1959
|
+
<option value="fortran">Lang: Fortran</option>
|
|
1960
|
+
<option value="r">Lang: R</option>
|
|
1961
|
+
<option value="matlab">Lang: MATLAB</option>
|
|
1962
|
+
</select>
|
|
1948
1963
|
</div>
|
|
1949
1964
|
</div>
|
|
1950
1965
|
<div id="sourceEditorWrap" class="editor-highlight-wrap">
|
|
@@ -2043,6 +2058,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2043
2058
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
2044
2059
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
2045
2060
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
2061
|
+
const langSelect = document.getElementById("langSelect");
|
|
2046
2062
|
|
|
2047
2063
|
const initialSourceState = {
|
|
2048
2064
|
source: (document.body && document.body.dataset && document.body.dataset.initialSource) || "blank",
|
|
@@ -2075,12 +2091,15 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2075
2091
|
let paneFocusTarget = "off";
|
|
2076
2092
|
const EDITOR_HIGHLIGHT_MAX_CHARS = 80_000;
|
|
2077
2093
|
const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
|
|
2094
|
+
const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
|
|
2095
|
+
const SUPPORTED_LANGUAGES = ["markdown", "javascript", "typescript", "python", "bash", "json", "rust", "c", "cpp", "julia", "fortran", "r", "matlab"];
|
|
2078
2096
|
const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
|
|
2079
2097
|
const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
|
|
2080
2098
|
let sourcePreviewRenderTimer = null;
|
|
2081
2099
|
let sourcePreviewRenderNonce = 0;
|
|
2082
2100
|
let responsePreviewRenderNonce = 0;
|
|
2083
2101
|
let editorHighlightEnabled = false;
|
|
2102
|
+
let editorLanguage = "markdown";
|
|
2084
2103
|
let responseHighlightEnabled = false;
|
|
2085
2104
|
let editorHighlightRenderRaf = null;
|
|
2086
2105
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
@@ -2470,10 +2489,14 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2470
2489
|
|
|
2471
2490
|
function renderSourcePreviewNow() {
|
|
2472
2491
|
if (editorView !== "preview") return;
|
|
2473
|
-
const
|
|
2492
|
+
const text = sourceTextEl.value || "";
|
|
2493
|
+
if (editorLanguage && editorLanguage !== "markdown") {
|
|
2494
|
+
sourcePreviewEl.innerHTML = "<div class='response-markdown-highlight' style='white-space:pre;font-family:var(--font-mono);font-size:13px;line-height:1.5;padding:16px;overflow:auto;'>" + highlightCode(text, editorLanguage) + "</div>";
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2474
2497
|
const nonce = ++sourcePreviewRenderNonce;
|
|
2475
2498
|
sourcePreviewEl.innerHTML = "<div class='preview-loading'>Rendering preview…</div>";
|
|
2476
|
-
void applyRenderedMarkdown(sourcePreviewEl,
|
|
2499
|
+
void applyRenderedMarkdown(sourcePreviewEl, text, "source", nonce);
|
|
2477
2500
|
}
|
|
2478
2501
|
|
|
2479
2502
|
function scheduleSourcePreviewRender(delayMs) {
|
|
@@ -2598,6 +2621,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2598
2621
|
sendRunBtn.disabled = uiBusy;
|
|
2599
2622
|
copyDraftBtn.disabled = uiBusy;
|
|
2600
2623
|
if (highlightSelect) highlightSelect.disabled = uiBusy;
|
|
2624
|
+
if (langSelect) langSelect.disabled = uiBusy;
|
|
2601
2625
|
editorViewSelect.disabled = uiBusy;
|
|
2602
2626
|
rightViewSelect.disabled = uiBusy;
|
|
2603
2627
|
followSelect.disabled = uiBusy;
|
|
@@ -2644,6 +2668,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2644
2668
|
}
|
|
2645
2669
|
|
|
2646
2670
|
updateEditorHighlightState();
|
|
2671
|
+
updateLangSelectVisibility();
|
|
2647
2672
|
}
|
|
2648
2673
|
|
|
2649
2674
|
function setRightView(nextView) {
|
|
@@ -2729,6 +2754,13 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2729
2754
|
if (first === "py" || first === "python") return "python";
|
|
2730
2755
|
if (first === "sh" || first === "bash" || first === "zsh" || first === "shell") return "bash";
|
|
2731
2756
|
if (first === "json" || first === "jsonc") return "json";
|
|
2757
|
+
if (first === "rust" || first === "rs") return "rust";
|
|
2758
|
+
if (first === "c" || first === "h") return "c";
|
|
2759
|
+
if (first === "cpp" || first === "c++" || first === "cxx" || first === "hpp") return "cpp";
|
|
2760
|
+
if (first === "julia" || first === "jl") return "julia";
|
|
2761
|
+
if (first === "fortran" || first === "f90" || first === "f95" || first === "f03" || first === "f" || first === "for") return "fortran";
|
|
2762
|
+
if (first === "r") return "r";
|
|
2763
|
+
if (first === "matlab" || first === "m") return "matlab";
|
|
2732
2764
|
|
|
2733
2765
|
return "";
|
|
2734
2766
|
}
|
|
@@ -2821,6 +2853,80 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2821
2853
|
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2822
2854
|
}
|
|
2823
2855
|
|
|
2856
|
+
if (lang === "rust") {
|
|
2857
|
+
const rustPattern = /(\\/\\/.*$)|("(?:[^"\\\\]|\\\\.)*")|(\\b(?:fn|let|mut|const|struct|enum|impl|trait|pub|mod|use|crate|self|super|match|if|else|for|while|loop|return|break|continue|where|as|in|ref|move|async|await|unsafe|extern|type|static|true|false|Some|None|Ok|Err|Self)\\b)|(\\b\\d[\\d_]*(?:\\.\\d[\\d_]*)?(?:f32|f64|u8|u16|u32|u64|u128|usize|i8|i16|i32|i64|i128|isize)?\\b)/g;
|
|
2858
|
+
const highlighted = highlightCodeTokens(source, rustPattern, (match) => {
|
|
2859
|
+
if (match[1]) return "hl-code-com";
|
|
2860
|
+
if (match[2]) return "hl-code-str";
|
|
2861
|
+
if (match[3]) return "hl-code-kw";
|
|
2862
|
+
if (match[4]) return "hl-code-num";
|
|
2863
|
+
return "hl-code";
|
|
2864
|
+
});
|
|
2865
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
if (lang === "c" || lang === "cpp") {
|
|
2869
|
+
const cPattern = /(\\/\\/.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)')|(#\\s*\\w+)|(\\b(?:if|else|for|while|do|switch|case|break|continue|return|goto|struct|union|enum|typedef|sizeof|void|int|char|short|long|float|double|unsigned|signed|const|static|extern|volatile|register|inline|auto|restrict|true|false|NULL|nullptr|class|public|private|protected|virtual|override|template|typename|namespace|using|new|delete|try|catch|throw|noexcept|constexpr|auto|decltype|static_cast|dynamic_cast|reinterpret_cast|const_cast|std|include|define|ifdef|ifndef|endif|pragma)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[fFlLuU]*\\b)/g;
|
|
2870
|
+
const highlighted = highlightCodeTokens(source, cPattern, (match) => {
|
|
2871
|
+
if (match[1]) return "hl-code-com";
|
|
2872
|
+
if (match[2]) return "hl-code-str";
|
|
2873
|
+
if (match[3]) return "hl-code-kw";
|
|
2874
|
+
if (match[4]) return "hl-code-kw";
|
|
2875
|
+
if (match[5]) return "hl-code-num";
|
|
2876
|
+
return "hl-code";
|
|
2877
|
+
});
|
|
2878
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
if (lang === "julia") {
|
|
2882
|
+
const jlPattern = /(#.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:function|end|if|elseif|else|for|while|begin|let|local|global|const|return|break|continue|do|try|catch|finally|throw|module|import|using|export|struct|mutable|abstract|primitive|where|macro|quote|true|false|nothing|missing|in|isa|typeof)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?\\b)/g;
|
|
2883
|
+
const highlighted = highlightCodeTokens(source, jlPattern, (match) => {
|
|
2884
|
+
if (match[1]) return "hl-code-com";
|
|
2885
|
+
if (match[2]) return "hl-code-str";
|
|
2886
|
+
if (match[3]) return "hl-code-kw";
|
|
2887
|
+
if (match[4]) return "hl-code-num";
|
|
2888
|
+
return "hl-code";
|
|
2889
|
+
});
|
|
2890
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
if (lang === "fortran") {
|
|
2894
|
+
const fPattern = /(!.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:program|end|subroutine|function|module|use|implicit|none|integer|real|double|precision|complex|character|logical|dimension|allocatable|intent|in|out|inout|parameter|data|do|if|then|else|elseif|endif|enddo|call|return|write|read|print|format|stop|contains|type|class|select|case|where|forall|associate|block|procedure|interface|abstract|extends|allocate|deallocate|cycle|exit|go|to|common|equivalence|save|external|intrinsic)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[dDeE][+-]?\\d+)?\\b)/gi;
|
|
2895
|
+
const highlighted = highlightCodeTokens(source, fPattern, (match) => {
|
|
2896
|
+
if (match[1]) return "hl-code-com";
|
|
2897
|
+
if (match[2]) return "hl-code-str";
|
|
2898
|
+
if (match[3]) return "hl-code-kw";
|
|
2899
|
+
if (match[4]) return "hl-code-num";
|
|
2900
|
+
return "hl-code";
|
|
2901
|
+
});
|
|
2902
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
if (lang === "r") {
|
|
2906
|
+
const rPattern = /(#.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:function|if|else|for|while|repeat|in|next|break|return|TRUE|FALSE|NULL|NA|NA_integer_|NA_real_|NA_complex_|NA_character_|Inf|NaN|library|require|source|local|switch)\\b)|(<-|->|<<-|->>)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[Li]?\\b)/g;
|
|
2907
|
+
const highlighted = highlightCodeTokens(source, rPattern, (match) => {
|
|
2908
|
+
if (match[1]) return "hl-code-com";
|
|
2909
|
+
if (match[2]) return "hl-code-str";
|
|
2910
|
+
if (match[3]) return "hl-code-kw";
|
|
2911
|
+
if (match[4]) return "hl-code-kw";
|
|
2912
|
+
if (match[5]) return "hl-code-num";
|
|
2913
|
+
return "hl-code";
|
|
2914
|
+
});
|
|
2915
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
if (lang === "matlab") {
|
|
2919
|
+
const matPattern = /(%.*$)|('(?:[^']|'')*'|"(?:[^"\\\\]|\\\\.)*")|(\\b(?:function|end|if|elseif|else|for|while|switch|case|otherwise|try|catch|return|break|continue|global|persistent|classdef|properties|methods|events|enumeration|true|false)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[i]?\\b)/g;
|
|
2920
|
+
const highlighted = highlightCodeTokens(source, matPattern, (match) => {
|
|
2921
|
+
if (match[1]) return "hl-code-com";
|
|
2922
|
+
if (match[2]) return "hl-code-str";
|
|
2923
|
+
if (match[3]) return "hl-code-kw";
|
|
2924
|
+
if (match[4]) return "hl-code-num";
|
|
2925
|
+
return "hl-code";
|
|
2926
|
+
});
|
|
2927
|
+
return "<span class='hl-code'>" + highlighted + "</span>";
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2824
2930
|
return wrapHighlight("hl-code", source);
|
|
2825
2931
|
}
|
|
2826
2932
|
|
|
@@ -2889,6 +2995,43 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2889
2995
|
return out.join("<br>");
|
|
2890
2996
|
}
|
|
2891
2997
|
|
|
2998
|
+
function highlightCode(text, language) {
|
|
2999
|
+
const lines = String(text || "").replace(/\\r\\n/g, "\\n").split("\\n");
|
|
3000
|
+
const lang = normalizeFenceLanguage(language);
|
|
3001
|
+
const out = [];
|
|
3002
|
+
for (const line of lines) {
|
|
3003
|
+
if (line.length === 0) {
|
|
3004
|
+
out.push("");
|
|
3005
|
+
} else if (lang) {
|
|
3006
|
+
out.push(highlightCodeLine(line, lang));
|
|
3007
|
+
} else {
|
|
3008
|
+
out.push(escapeHtml(line));
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return out.join("<br>");
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
function detectLanguageFromName(name) {
|
|
3015
|
+
if (!name) return "";
|
|
3016
|
+
const dot = name.lastIndexOf(".");
|
|
3017
|
+
if (dot < 0) return "";
|
|
3018
|
+
const ext = name.slice(dot + 1).toLowerCase();
|
|
3019
|
+
if (ext === "js" || ext === "mjs" || ext === "cjs" || ext === "jsx") return "javascript";
|
|
3020
|
+
if (ext === "ts" || ext === "mts" || ext === "cts" || ext === "tsx") return "typescript";
|
|
3021
|
+
if (ext === "py" || ext === "pyw") return "python";
|
|
3022
|
+
if (ext === "sh" || ext === "bash" || ext === "zsh") return "bash";
|
|
3023
|
+
if (ext === "json" || ext === "jsonc" || ext === "json5") return "json";
|
|
3024
|
+
if (ext === "rs") return "rust";
|
|
3025
|
+
if (ext === "c" || ext === "h") return "c";
|
|
3026
|
+
if (ext === "cpp" || ext === "cxx" || ext === "cc" || ext === "hpp" || ext === "hxx") return "cpp";
|
|
3027
|
+
if (ext === "jl") return "julia";
|
|
3028
|
+
if (ext === "f90" || ext === "f95" || ext === "f03" || ext === "f" || ext === "for") return "fortran";
|
|
3029
|
+
if (ext === "r" || ext === "R") return "r";
|
|
3030
|
+
if (ext === "m") return "matlab";
|
|
3031
|
+
if (ext === "md" || ext === "markdown" || ext === "mdx") return "markdown";
|
|
3032
|
+
return "";
|
|
3033
|
+
}
|
|
3034
|
+
|
|
2892
3035
|
function renderEditorHighlightNow() {
|
|
2893
3036
|
if (!sourceHighlightEl) return;
|
|
2894
3037
|
if (!editorHighlightEnabled || editorView !== "markdown") {
|
|
@@ -2903,7 +3046,11 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
2903
3046
|
return;
|
|
2904
3047
|
}
|
|
2905
3048
|
|
|
2906
|
-
|
|
3049
|
+
if (editorLanguage === "markdown" || !editorLanguage) {
|
|
3050
|
+
sourceHighlightEl.innerHTML = highlightMarkdown(text);
|
|
3051
|
+
} else {
|
|
3052
|
+
sourceHighlightEl.innerHTML = highlightCode(text, editorLanguage);
|
|
3053
|
+
}
|
|
2907
3054
|
syncEditorHighlightScroll();
|
|
2908
3055
|
}
|
|
2909
3056
|
|
|
@@ -3008,6 +3155,46 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
3008
3155
|
highlightSelect.value = editorHighlightEnabled ? "on" : "off";
|
|
3009
3156
|
}
|
|
3010
3157
|
updateEditorHighlightState();
|
|
3158
|
+
updateLangSelectVisibility();
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
function readStoredEditorLanguage() {
|
|
3162
|
+
if (!window.localStorage) return null;
|
|
3163
|
+
try {
|
|
3164
|
+
const value = window.localStorage.getItem(EDITOR_LANGUAGE_STORAGE_KEY);
|
|
3165
|
+
if (value && SUPPORTED_LANGUAGES.indexOf(value) !== -1) return value;
|
|
3166
|
+
return null;
|
|
3167
|
+
} catch {
|
|
3168
|
+
return null;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
function persistEditorLanguage(lang) {
|
|
3173
|
+
if (!window.localStorage) return;
|
|
3174
|
+
try {
|
|
3175
|
+
window.localStorage.setItem(EDITOR_LANGUAGE_STORAGE_KEY, lang || "markdown");
|
|
3176
|
+
} catch {}
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
function setEditorLanguage(lang) {
|
|
3180
|
+
editorLanguage = (lang && SUPPORTED_LANGUAGES.indexOf(lang) !== -1) ? lang : "markdown";
|
|
3181
|
+
persistEditorLanguage(editorLanguage);
|
|
3182
|
+
if (langSelect) {
|
|
3183
|
+
langSelect.value = editorLanguage;
|
|
3184
|
+
}
|
|
3185
|
+
if (editorHighlightEnabled && editorView === "markdown") {
|
|
3186
|
+
scheduleEditorHighlightRender();
|
|
3187
|
+
}
|
|
3188
|
+
if (editorView === "preview") {
|
|
3189
|
+
scheduleSourcePreviewRender(0);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
function updateLangSelectVisibility() {
|
|
3194
|
+
if (!langSelect) return;
|
|
3195
|
+
const highlightActive = editorHighlightEnabled && editorView === "markdown";
|
|
3196
|
+
const previewActive = editorView === "preview";
|
|
3197
|
+
langSelect.hidden = !(highlightActive || previewActive);
|
|
3011
3198
|
}
|
|
3012
3199
|
|
|
3013
3200
|
function setResponseHighlightEnabled(enabled) {
|
|
@@ -3468,6 +3655,12 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
3468
3655
|
});
|
|
3469
3656
|
}
|
|
3470
3657
|
|
|
3658
|
+
if (langSelect) {
|
|
3659
|
+
langSelect.addEventListener("change", () => {
|
|
3660
|
+
setEditorLanguage(langSelect.value);
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3471
3664
|
pullLatestBtn.addEventListener("click", () => {
|
|
3472
3665
|
if (queuedLatestResponse) {
|
|
3473
3666
|
if (applyLatestPayload(queuedLatestResponse)) {
|
|
@@ -3703,6 +3896,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
3703
3896
|
path: null,
|
|
3704
3897
|
});
|
|
3705
3898
|
refreshResponseUi();
|
|
3899
|
+
const detectedLang = detectLanguageFromName(file.name);
|
|
3900
|
+
if (detectedLang) {
|
|
3901
|
+
setEditorLanguage(detectedLang);
|
|
3902
|
+
}
|
|
3706
3903
|
setStatus("Loaded file " + file.name + ".", "success");
|
|
3707
3904
|
};
|
|
3708
3905
|
reader.onerror = () => {
|
|
@@ -3719,6 +3916,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
|
|
|
3719
3916
|
const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value === "on");
|
|
3720
3917
|
setEditorHighlightEnabled(initialHighlightEnabled);
|
|
3721
3918
|
|
|
3919
|
+
const initialDetectedLang = detectLanguageFromName(initialSourceState.path || initialSourceState.label || "");
|
|
3920
|
+
const storedLang = readStoredEditorLanguage();
|
|
3921
|
+
setEditorLanguage(initialDetectedLang || storedLang || "markdown");
|
|
3922
|
+
|
|
3722
3923
|
const storedResponseHighlightEnabled = readStoredResponseHighlightEnabled();
|
|
3723
3924
|
const initialResponseHighlightEnabled = storedResponseHighlightEnabled ?? Boolean(responseHighlightSelect && responseHighlightSelect.value === "on");
|
|
3724
3925
|
setResponseHighlightEnabled(initialResponseHighlightEnabled);
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|