pi-studio 0.5.40 → 0.5.42
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/client/studio-client.js +133 -5
- package/client/studio.css +4 -0
- package/index.ts +153 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.42] — 2026-03-31
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Studio now includes a **Refresh from disk** action for file-backed documents, including files originally opened from disk and documents later saved to disk from Studio.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `/studio-pdf` section, subsection, and deeper heading styling is now more robust for larger-font exports: headings avoid awkward hyphenation, paragraph-level headings (`####`) render cleanly, callout title badges scale better with larger body font sizes, and exported code blocks now use a subtle shaded background.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Re-selecting the same file in **Load file content** now reloads it reliably instead of sometimes doing nothing.
|
|
17
|
+
- `/studio-pdf` now supports LaTeX `[H]` float placement in exported documents.
|
|
18
|
+
- Response preview now resets to the top more reliably for genuinely new replies, while keeping editor preview behavior unchanged.
|
|
19
|
+
|
|
20
|
+
## [0.5.41] — 2026-03-30
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `/studio-pdf` PDF callouts now render with a slightly stronger visual treatment in exported PDFs, including per-kind colours and a more obvious title badge, while staying page-break-friendly.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- `/studio-pdf` fenced Quarto-style callouts that end with lists now keep their marker paragraphs separate during Pandoc processing, avoiding malformed LaTeX such as callouts closing inside the final list item.
|
|
27
|
+
|
|
7
28
|
## [0.5.40] — 2026-03-30
|
|
8
29
|
|
|
9
30
|
### Changed
|
package/client/studio-client.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
const rightPaneEl = document.getElementById("rightPane");
|
|
47
47
|
const sourceBadgeEl = document.getElementById("sourceBadge");
|
|
48
48
|
const syncBadgeEl = document.getElementById("syncBadge");
|
|
49
|
-
|
|
49
|
+
let critiqueViewEl = document.getElementById("critiqueView");
|
|
50
50
|
const referenceBadgeEl = document.getElementById("referenceBadge");
|
|
51
51
|
const editorViewSelect = document.getElementById("editorViewSelect");
|
|
52
52
|
const rightViewSelect = document.getElementById("rightViewSelect");
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
const loadHistoryPromptBtn = document.getElementById("loadHistoryPromptBtn");
|
|
75
75
|
const saveAsBtn = document.getElementById("saveAsBtn");
|
|
76
76
|
const saveOverBtn = document.getElementById("saveOverBtn");
|
|
77
|
+
const refreshFromDiskBtn = document.getElementById("refreshFromDiskBtn");
|
|
77
78
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
78
79
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
79
80
|
const loadGitDiffBtn = document.getElementById("loadGitDiffBtn");
|
|
@@ -196,6 +197,7 @@
|
|
|
196
197
|
label: initialSourceState.label,
|
|
197
198
|
path: initialSourceState.path,
|
|
198
199
|
};
|
|
200
|
+
let fileBackedBaselineText = null;
|
|
199
201
|
let activePane = "left";
|
|
200
202
|
let paneFocusTarget = "off";
|
|
201
203
|
const EDITOR_HIGHLIGHT_MAX_CHARS = 100_000;
|
|
@@ -427,6 +429,7 @@
|
|
|
427
429
|
if (kind === "send_to_editor") return "sending to pi editor";
|
|
428
430
|
if (kind === "get_from_editor") return "loading from pi editor";
|
|
429
431
|
if (kind === "load_git_diff") return "loading git diff";
|
|
432
|
+
if (kind === "refresh_from_disk") return "refreshing from disk";
|
|
430
433
|
if (kind === "save_as" || kind === "save_over") return "saving editor text";
|
|
431
434
|
return "submitting request";
|
|
432
435
|
}
|
|
@@ -779,11 +782,29 @@
|
|
|
779
782
|
}
|
|
780
783
|
});
|
|
781
784
|
|
|
785
|
+
function markFileBackedBaseline(text) {
|
|
786
|
+
fileBackedBaselineText = String(text || "");
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function clearFileBackedBaseline() {
|
|
790
|
+
fileBackedBaselineText = null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function hasRefreshableFilePath() {
|
|
794
|
+
return Boolean(sourceState && sourceState.path);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function editorDiffersFromFileBackedBaseline() {
|
|
798
|
+
if (!hasRefreshableFilePath()) return false;
|
|
799
|
+
if (fileBackedBaselineText === null) return true;
|
|
800
|
+
return sourceTextEl.value !== fileBackedBaselineText;
|
|
801
|
+
}
|
|
802
|
+
|
|
782
803
|
function updateSourceBadge() {
|
|
783
804
|
const label = sourceState && sourceState.label ? sourceState.label : "blank";
|
|
784
805
|
sourceBadgeEl.textContent = "Editor origin: " + label;
|
|
785
806
|
// Show "Set working dir" button when not file-backed
|
|
786
|
-
var isFileBacked =
|
|
807
|
+
var isFileBacked = hasRefreshableFilePath();
|
|
787
808
|
if (isFileBacked) {
|
|
788
809
|
if (resourceDirInput) resourceDirInput.value = "";
|
|
789
810
|
if (resourceDirLabel) resourceDirLabel.textContent = "";
|
|
@@ -1926,12 +1947,52 @@
|
|
|
1926
1947
|
});
|
|
1927
1948
|
}
|
|
1928
1949
|
|
|
1950
|
+
function replaceResponsePaneWithClone() {
|
|
1951
|
+
const currentEl = critiqueViewEl;
|
|
1952
|
+
if (!currentEl || !currentEl.parentNode || typeof currentEl.cloneNode !== "function") {
|
|
1953
|
+
return currentEl;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
const replacement = currentEl.cloneNode(true);
|
|
1957
|
+
if (!replacement || replacement.nodeType !== 1) {
|
|
1958
|
+
return currentEl;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
currentEl.parentNode.replaceChild(replacement, currentEl);
|
|
1962
|
+
critiqueViewEl = replacement;
|
|
1963
|
+
return critiqueViewEl;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1929
1966
|
function applyPendingResponseScrollReset() {
|
|
1930
1967
|
if (!pendingResponseScrollReset || !critiqueViewEl) return false;
|
|
1931
1968
|
if (rightView === "editor-preview") return false;
|
|
1932
|
-
|
|
1933
|
-
critiqueViewEl.scrollLeft = 0;
|
|
1969
|
+
|
|
1934
1970
|
pendingResponseScrollReset = false;
|
|
1971
|
+
let targetEl = replaceResponsePaneWithClone();
|
|
1972
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
1973
|
+
? window.requestAnimationFrame.bind(window)
|
|
1974
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
1975
|
+
const resetScroll = () => {
|
|
1976
|
+
if (!targetEl || !targetEl.isConnected) return;
|
|
1977
|
+
if (rightView === "editor-preview") return;
|
|
1978
|
+
targetEl.scrollTop = 0;
|
|
1979
|
+
targetEl.scrollLeft = 0;
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
if (targetEl && targetEl.classList) {
|
|
1983
|
+
targetEl.classList.add("response-scroll-resetting");
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
resetScroll();
|
|
1987
|
+
schedule(() => {
|
|
1988
|
+
resetScroll();
|
|
1989
|
+
schedule(() => {
|
|
1990
|
+
resetScroll();
|
|
1991
|
+
if (targetEl && targetEl.classList) {
|
|
1992
|
+
targetEl.classList.remove("response-scroll-resetting");
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
});
|
|
1935
1996
|
return true;
|
|
1936
1997
|
}
|
|
1937
1998
|
|
|
@@ -2518,7 +2579,7 @@
|
|
|
2518
2579
|
|
|
2519
2580
|
function getEffectiveSavePath() {
|
|
2520
2581
|
// File-backed: use the original path
|
|
2521
|
-
if (sourceState.
|
|
2582
|
+
if (sourceState.path) return sourceState.path;
|
|
2522
2583
|
// Upload with working dir + filename: derive path
|
|
2523
2584
|
if (sourceState.source === "upload" && sourceState.label && resourceDirInput && resourceDirInput.value.trim()) {
|
|
2524
2585
|
var name = sourceState.label.replace(/^upload:\s*/i, "");
|
|
@@ -2557,12 +2618,25 @@
|
|
|
2557
2618
|
saveOverBtn.title = "Save editor is available after opening a file, setting a working dir, or using Save editor as…. Shortcut: Cmd/Ctrl+S falls back to Save editor as… when needed.";
|
|
2558
2619
|
}
|
|
2559
2620
|
|
|
2621
|
+
function updateRefreshFromDiskTooltip() {
|
|
2622
|
+
if (!refreshFromDiskBtn) return;
|
|
2623
|
+
|
|
2624
|
+
if (hasRefreshableFilePath()) {
|
|
2625
|
+
refreshFromDiskBtn.title = "Reload the current file-backed document from disk: " + sourceState.path;
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
refreshFromDiskBtn.title = "Refresh from disk is only available for documents that currently have a file path.";
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2560
2632
|
function syncActionButtons() {
|
|
2561
2633
|
const canSaveOver = Boolean(getEffectiveSavePath());
|
|
2634
|
+
const canRefreshFromDisk = hasRefreshableFilePath();
|
|
2562
2635
|
|
|
2563
2636
|
fileInput.disabled = uiBusy;
|
|
2564
2637
|
saveAsBtn.disabled = uiBusy;
|
|
2565
2638
|
saveOverBtn.disabled = uiBusy || !canSaveOver;
|
|
2639
|
+
if (refreshFromDiskBtn) refreshFromDiskBtn.disabled = uiBusy || !canRefreshFromDisk;
|
|
2566
2640
|
sendEditorBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
2567
2641
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
2568
2642
|
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
@@ -2581,6 +2655,7 @@
|
|
|
2581
2655
|
insertHeaderBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
2582
2656
|
lensSelect.disabled = uiBusy || isEditorOnlyMode;
|
|
2583
2657
|
updateSaveFileTooltip();
|
|
2658
|
+
updateRefreshFromDiskTooltip();
|
|
2584
2659
|
updateHistoryControls();
|
|
2585
2660
|
updateResultActionButtons();
|
|
2586
2661
|
}
|
|
@@ -2598,6 +2673,9 @@
|
|
|
2598
2673
|
label: next && next.label ? next.label : "blank",
|
|
2599
2674
|
path: next && next.path ? next.path : null,
|
|
2600
2675
|
};
|
|
2676
|
+
if (!sourceState.path) {
|
|
2677
|
+
clearFileBackedBaseline();
|
|
2678
|
+
}
|
|
2601
2679
|
updateSourceBadge();
|
|
2602
2680
|
syncActionButtons();
|
|
2603
2681
|
}
|
|
@@ -3778,6 +3856,9 @@
|
|
|
3778
3856
|
label: message.initialDocument.label || "blank",
|
|
3779
3857
|
path: message.initialDocument.path || null,
|
|
3780
3858
|
});
|
|
3859
|
+
if (message.initialDocument.path) {
|
|
3860
|
+
markFileBackedBaseline(message.initialDocument.text);
|
|
3861
|
+
}
|
|
3781
3862
|
refreshResponseUi();
|
|
3782
3863
|
if (typeof message.initialDocument.label === "string" && message.initialDocument.label.length > 0) {
|
|
3783
3864
|
setStatus("Loaded " + message.initialDocument.label + ".", "success");
|
|
@@ -3978,6 +4059,8 @@
|
|
|
3978
4059
|
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
3979
4060
|
pendingRequestId = null;
|
|
3980
4061
|
pendingKind = null;
|
|
4062
|
+
clearArmedTitleAttention(message.requestId);
|
|
4063
|
+
stickyStudioKind = null;
|
|
3981
4064
|
}
|
|
3982
4065
|
if (message.path) {
|
|
3983
4066
|
setSourceState({
|
|
@@ -3985,6 +4068,7 @@
|
|
|
3985
4068
|
label: message.label || message.path,
|
|
3986
4069
|
path: message.path,
|
|
3987
4070
|
});
|
|
4071
|
+
markFileBackedBaseline(sourceTextEl.value);
|
|
3988
4072
|
}
|
|
3989
4073
|
setBusy(false);
|
|
3990
4074
|
setWsState("Ready");
|
|
@@ -4032,6 +4116,15 @@
|
|
|
4032
4116
|
return;
|
|
4033
4117
|
}
|
|
4034
4118
|
|
|
4119
|
+
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
4120
|
+
pendingRequestId = null;
|
|
4121
|
+
pendingKind = null;
|
|
4122
|
+
clearArmedTitleAttention(message.requestId);
|
|
4123
|
+
stickyStudioKind = null;
|
|
4124
|
+
setBusy(false);
|
|
4125
|
+
setWsState("Ready");
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4035
4128
|
const nextSource =
|
|
4036
4129
|
nextDoc.source === "file" || nextDoc.source === "last-response"
|
|
4037
4130
|
? nextDoc.source
|
|
@@ -4045,6 +4138,9 @@
|
|
|
4045
4138
|
|
|
4046
4139
|
setEditorText(nextDoc.text, { preserveScroll: false, preserveSelection: false });
|
|
4047
4140
|
setSourceState({ source: nextSource, label: nextLabel, path: nextPath });
|
|
4141
|
+
if (nextPath) {
|
|
4142
|
+
markFileBackedBaseline(nextDoc.text);
|
|
4143
|
+
}
|
|
4048
4144
|
refreshResponseUi();
|
|
4049
4145
|
setStatus(
|
|
4050
4146
|
typeof message.message === "string" && message.message.trim()
|
|
@@ -4828,6 +4924,34 @@
|
|
|
4828
4924
|
}
|
|
4829
4925
|
});
|
|
4830
4926
|
|
|
4927
|
+
if (refreshFromDiskBtn) {
|
|
4928
|
+
refreshFromDiskBtn.addEventListener("click", () => {
|
|
4929
|
+
if (!hasRefreshableFilePath()) {
|
|
4930
|
+
setStatus("Refresh from disk is only available for file-backed documents.", "warning");
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4934
|
+
if (editorDiffersFromFileBackedBaseline()) {
|
|
4935
|
+
const confirmed = window.confirm("Replace current editor contents with the latest version from disk?");
|
|
4936
|
+
if (!confirmed) return;
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
const requestId = beginUiAction("refresh_from_disk");
|
|
4940
|
+
if (!requestId) return;
|
|
4941
|
+
|
|
4942
|
+
const sent = sendMessage({
|
|
4943
|
+
type: "refresh_from_disk_request",
|
|
4944
|
+
requestId,
|
|
4945
|
+
});
|
|
4946
|
+
|
|
4947
|
+
if (!sent) {
|
|
4948
|
+
pendingRequestId = null;
|
|
4949
|
+
pendingKind = null;
|
|
4950
|
+
setBusy(false);
|
|
4951
|
+
}
|
|
4952
|
+
});
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4831
4955
|
sendEditorBtn.addEventListener("click", () => {
|
|
4832
4956
|
const content = sourceTextEl.value;
|
|
4833
4957
|
if (!content.trim()) {
|
|
@@ -5136,6 +5260,10 @@
|
|
|
5136
5260
|
const file = fileInput.files && fileInput.files[0];
|
|
5137
5261
|
if (!file) return;
|
|
5138
5262
|
|
|
5263
|
+
// Clear the input immediately so selecting the same file again will
|
|
5264
|
+
// still fire a future change event.
|
|
5265
|
+
fileInput.value = "";
|
|
5266
|
+
|
|
5139
5267
|
const reader = new FileReader();
|
|
5140
5268
|
reader.onload = () => {
|
|
5141
5269
|
const text = typeof reader.result === "string" ? reader.result : "";
|
package/client/studio.css
CHANGED
package/index.ts
CHANGED
|
@@ -159,6 +159,11 @@ interface SaveOverRequestMessage {
|
|
|
159
159
|
content: string;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
interface RefreshFromDiskRequestMessage {
|
|
163
|
+
type: "refresh_from_disk_request";
|
|
164
|
+
requestId: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
162
167
|
interface SendToEditorRequestMessage {
|
|
163
168
|
type: "send_to_editor_request";
|
|
164
169
|
requestId: string;
|
|
@@ -192,6 +197,7 @@ type IncomingStudioMessage =
|
|
|
192
197
|
| CompactRequestMessage
|
|
193
198
|
| SaveAsRequestMessage
|
|
194
199
|
| SaveOverRequestMessage
|
|
200
|
+
| RefreshFromDiskRequestMessage
|
|
195
201
|
| SendToEditorRequestMessage
|
|
196
202
|
| GetFromEditorRequestMessage
|
|
197
203
|
| LoadGitDiffRequestMessage
|
|
@@ -234,36 +240,65 @@ function buildStudioPdfTitleSpacingLength(value: string | undefined, fallback: s
|
|
|
234
240
|
return trimmed || fallback;
|
|
235
241
|
}
|
|
236
242
|
|
|
243
|
+
function buildStudioPdfCalloutTitleSizeCommand(options?: StudioPdfRenderOptions): string {
|
|
244
|
+
const sizePt = getStudioRequestedPdfFontsizePt(options);
|
|
245
|
+
if (sizePt && sizePt >= 14) return "\\normalsize";
|
|
246
|
+
if (sizePt && sizePt >= 13) return "\\small";
|
|
247
|
+
return "\\footnotesize";
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
function buildStudioPdfPreamble(options?: StudioPdfRenderOptions): string {
|
|
238
251
|
const sectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.sectionSize, "\\Large");
|
|
239
252
|
const subsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsectionSize, "\\large");
|
|
240
253
|
const subsubsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsubsectionSize, "\\normalsize");
|
|
254
|
+
const calloutTitleSize = buildStudioPdfCalloutTitleSizeCommand(options);
|
|
241
255
|
const sectionSpaceBefore = buildStudioPdfTitleSpacingLength(options?.sectionSpaceBefore, "1.5ex plus 0.5ex minus 0.2ex");
|
|
242
256
|
const sectionSpaceAfter = buildStudioPdfTitleSpacingLength(options?.sectionSpaceAfter, "1ex plus 0.2ex");
|
|
243
257
|
const subsectionSpaceBefore = buildStudioPdfTitleSpacingLength(options?.subsectionSpaceBefore, "1.2ex plus 0.4ex minus 0.2ex");
|
|
244
258
|
const subsectionSpaceAfter = buildStudioPdfTitleSpacingLength(options?.subsectionSpaceAfter, "0.6ex plus 0.1ex");
|
|
245
259
|
return `\\usepackage{titlesec}
|
|
246
|
-
\\titleformat{\\section}{${sectionHeadingSize}\\bfseries\\sffamily}{}{0pt}{}[\\vspace{3pt}\\titlerule\\vspace{12pt}]
|
|
247
|
-
\\titleformat{\\subsection}{${subsectionHeadingSize}\\bfseries\\sffamily}{}{0pt}{}
|
|
248
|
-
\\titleformat{\\subsubsection}{${subsubsectionHeadingSize}\\bfseries\\sffamily}{}{0pt}{}
|
|
260
|
+
\\titleformat{\\section}{${sectionHeadingSize}\\bfseries\\sffamily\\raggedright\\hyphenpenalty=10000\\exhyphenpenalty=10000\\relax}{}{0pt}{}[\\vspace{3pt}\\titlerule\\vspace{12pt}]
|
|
261
|
+
\\titleformat{\\subsection}{${subsectionHeadingSize}\\bfseries\\sffamily\\raggedright\\hyphenpenalty=10000\\exhyphenpenalty=10000\\relax}{}{0pt}{}
|
|
262
|
+
\\titleformat{\\subsubsection}{${subsubsectionHeadingSize}\\bfseries\\sffamily\\raggedright\\hyphenpenalty=10000\\exhyphenpenalty=10000\\relax}{}{0pt}{}
|
|
263
|
+
\\titleformat{\\paragraph}[runin]{\\normalsize\\bfseries\\sffamily\\raggedright\\hyphenpenalty=10000\\exhyphenpenalty=10000\\relax}{}{0pt}{}
|
|
264
|
+
\\titleformat{\\subparagraph}[runin]{\\small\\bfseries\\sffamily\\raggedright\\hyphenpenalty=10000\\exhyphenpenalty=10000\\relax}{}{0pt}{}
|
|
249
265
|
\\titlespacing*{\\section}{0pt}{${sectionSpaceBefore}}{${sectionSpaceAfter}}
|
|
250
266
|
\\titlespacing*{\\subsection}{0pt}{${subsectionSpaceBefore}}{${subsectionSpaceAfter}}
|
|
267
|
+
\\titlespacing*{\\paragraph}{0pt}{0.9ex plus 0.3ex minus 0.1ex}{0.8em}
|
|
268
|
+
\\titlespacing*{\\subparagraph}{0pt}{0.7ex plus 0.2ex minus 0.1ex}{0.7em}
|
|
251
269
|
\\usepackage{xcolor}
|
|
252
270
|
\\usepackage{varwidth}
|
|
253
271
|
\\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
|
|
254
272
|
\\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
|
|
255
273
|
\\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
|
|
274
|
+
\\definecolor{StudioCodeBlockBg}{HTML}{F6F8FA}
|
|
256
275
|
\\definecolor{StudioDiffAddText}{HTML}{1A7F37}
|
|
257
276
|
\\definecolor{StudioDiffDelText}{HTML}{CF222E}
|
|
258
277
|
\\definecolor{StudioDiffMetaText}{HTML}{57606A}
|
|
259
278
|
\\definecolor{StudioDiffHunkText}{HTML}{0969DA}
|
|
279
|
+
\\definecolor{StudioCalloutNoteBorder}{HTML}{2F6FEB}
|
|
280
|
+
\\definecolor{StudioCalloutNoteText}{HTML}{1F4B99}
|
|
281
|
+
\\definecolor{StudioCalloutNoteLabelBg}{HTML}{EAF2FF}
|
|
282
|
+
\\definecolor{StudioCalloutTipBorder}{HTML}{1A7F37}
|
|
283
|
+
\\definecolor{StudioCalloutTipText}{HTML}{175C2C}
|
|
284
|
+
\\definecolor{StudioCalloutTipLabelBg}{HTML}{EAF7EE}
|
|
285
|
+
\\definecolor{StudioCalloutWarningBorder}{HTML}{B76E00}
|
|
286
|
+
\\definecolor{StudioCalloutWarningText}{HTML}{8A5300}
|
|
287
|
+
\\definecolor{StudioCalloutWarningLabelBg}{HTML}{FFF3D6}
|
|
288
|
+
\\definecolor{StudioCalloutImportantBorder}{HTML}{CF222E}
|
|
289
|
+
\\definecolor{StudioCalloutImportantText}{HTML}{A40E26}
|
|
290
|
+
\\definecolor{StudioCalloutImportantLabelBg}{HTML}{FDEBEC}
|
|
291
|
+
\\definecolor{StudioCalloutCautionBorder}{HTML}{CF222E}
|
|
292
|
+
\\definecolor{StudioCalloutCautionText}{HTML}{A40E26}
|
|
293
|
+
\\definecolor{StudioCalloutCautionLabelBg}{HTML}{FDEBEC}
|
|
260
294
|
\\newcommand{\\studioannotation}[1]{\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\begin{varwidth}{\\dimexpr\\linewidth-2\\fboxsep-2\\fboxrule\\relax}\\raggedright\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}\\end{varwidth}}\\endgroup}
|
|
261
295
|
\\newcommand{\\StudioDiffAddTok}[1]{\\textcolor{StudioDiffAddText}{#1}}
|
|
262
296
|
\\newcommand{\\StudioDiffDelTok}[1]{\\textcolor{StudioDiffDelText}{#1}}
|
|
263
297
|
\\newcommand{\\StudioDiffMetaTok}[1]{\\textcolor{StudioDiffMetaText}{#1}}
|
|
264
298
|
\\newcommand{\\StudioDiffHunkTok}[1]{\\textcolor{StudioDiffHunkText}{#1}}
|
|
265
299
|
\\newcommand{\\StudioDiffHeaderTok}[1]{\\textcolor{StudioDiffHunkText}{\\textbf{#1}}}
|
|
266
|
-
\\newenvironment{studiocallout}[
|
|
300
|
+
\\newenvironment{studiocallout}[4]{\\par\\vspace{0.6em}\\noindent\\begingroup\\def\\StudioCalloutBorder{#2}\\def\\StudioCalloutText{#3}\\def\\StudioCalloutLabelBg{#4}\\color{\\StudioCalloutBorder}\\hrule height 0.8pt\\relax\\vspace{0.32em}\\noindent\\colorbox{\\StudioCalloutLabelBg}{\\strut\\hspace{0.55em}{${calloutTitleSize}\\sffamily\\bfseries\\textcolor{\\StudioCalloutText}{#1}}\\hspace{0.55em}}\\par\\vspace{0.24em}\\normalcolor\\leftskip=0.9em\\rightskip=0pt\\parindent=0pt\\parskip=0.18em}{\\par\\vspace{0.12em}\\noindent\\color{\\StudioCalloutBorder}\\hrule height 0.55pt\\par\\endgroup\\vspace{0.5em}}
|
|
301
|
+
\\usepackage{float}
|
|
267
302
|
\\usepackage{caption}
|
|
268
303
|
\\captionsetup[figure]{justification=raggedright,singlelinecheck=false}
|
|
269
304
|
\\usepackage{enumitem}
|
|
@@ -273,9 +308,9 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions): string {
|
|
|
273
308
|
\\usepackage{fvextra}
|
|
274
309
|
\\makeatletter
|
|
275
310
|
\\@ifundefined{Highlighting}{%
|
|
276
|
-
\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},breaklines,breakanywhere}%
|
|
311
|
+
\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},breaklines,breakanywhere,bgcolor=StudioCodeBlockBg,framesep=2mm}%
|
|
277
312
|
}{%
|
|
278
|
-
\\RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},breaklines,breakanywhere}%
|
|
313
|
+
\\RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},breaklines,breakanywhere,bgcolor=StudioCodeBlockBg,framesep=2mm}%
|
|
279
314
|
}
|
|
280
315
|
\\makeatother
|
|
281
316
|
`;
|
|
@@ -3405,9 +3440,19 @@ function preprocessStudioMarkdownCalloutsForPdf(markdown: string): { markdown: s
|
|
|
3405
3440
|
content: contentLines.join("\n").trim(),
|
|
3406
3441
|
};
|
|
3407
3442
|
blocks.push(block);
|
|
3443
|
+
// Keep markers on their own paragraphs so pandoc does not absorb them
|
|
3444
|
+
// into neighbouring list items or paragraphs. Without these blank
|
|
3445
|
+
// lines, the end marker for a callout that finishes with a list can be
|
|
3446
|
+
// emitted inside the final \item, which then produces malformed LaTeX
|
|
3447
|
+
// when we later replace the marker range with a custom callout
|
|
3448
|
+
// environment.
|
|
3449
|
+
out.push("");
|
|
3408
3450
|
out.push(`PISTUDIOPDFCALLOUTSTART${block.kind.toUpperCase()}${block.markerId}`);
|
|
3451
|
+
out.push("");
|
|
3409
3452
|
if (block.content) out.push(block.content);
|
|
3453
|
+
out.push("");
|
|
3410
3454
|
out.push(`PISTUDIOPDFCALLOUTEND${block.kind.toUpperCase()}${block.markerId}`);
|
|
3455
|
+
out.push("");
|
|
3411
3456
|
i = j;
|
|
3412
3457
|
}
|
|
3413
3458
|
|
|
@@ -3480,6 +3525,52 @@ function preprocessStudioMarkdownImageAlignmentForPdf(markdown: string): { markd
|
|
|
3480
3525
|
return { markdown: out.join("\n"), blocks };
|
|
3481
3526
|
}
|
|
3482
3527
|
|
|
3528
|
+
function getStudioPdfCalloutStyle(kind: StudioPdfMarkdownCalloutBlock["kind"]): {
|
|
3529
|
+
label: string;
|
|
3530
|
+
borderColor: string;
|
|
3531
|
+
textColor: string;
|
|
3532
|
+
labelBgColor: string;
|
|
3533
|
+
} {
|
|
3534
|
+
switch (kind) {
|
|
3535
|
+
case "note":
|
|
3536
|
+
return {
|
|
3537
|
+
label: "Note",
|
|
3538
|
+
borderColor: "StudioCalloutNoteBorder",
|
|
3539
|
+
textColor: "StudioCalloutNoteText",
|
|
3540
|
+
labelBgColor: "StudioCalloutNoteLabelBg",
|
|
3541
|
+
};
|
|
3542
|
+
case "tip":
|
|
3543
|
+
return {
|
|
3544
|
+
label: "Tip",
|
|
3545
|
+
borderColor: "StudioCalloutTipBorder",
|
|
3546
|
+
textColor: "StudioCalloutTipText",
|
|
3547
|
+
labelBgColor: "StudioCalloutTipLabelBg",
|
|
3548
|
+
};
|
|
3549
|
+
case "warning":
|
|
3550
|
+
return {
|
|
3551
|
+
label: "Warning",
|
|
3552
|
+
borderColor: "StudioCalloutWarningBorder",
|
|
3553
|
+
textColor: "StudioCalloutWarningText",
|
|
3554
|
+
labelBgColor: "StudioCalloutWarningLabelBg",
|
|
3555
|
+
};
|
|
3556
|
+
case "important":
|
|
3557
|
+
return {
|
|
3558
|
+
label: "Important",
|
|
3559
|
+
borderColor: "StudioCalloutImportantBorder",
|
|
3560
|
+
textColor: "StudioCalloutImportantText",
|
|
3561
|
+
labelBgColor: "StudioCalloutImportantLabelBg",
|
|
3562
|
+
};
|
|
3563
|
+
case "caution":
|
|
3564
|
+
default:
|
|
3565
|
+
return {
|
|
3566
|
+
label: "Caution",
|
|
3567
|
+
borderColor: "StudioCalloutCautionBorder",
|
|
3568
|
+
textColor: "StudioCalloutCautionText",
|
|
3569
|
+
labelBgColor: "StudioCalloutCautionLabelBg",
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3483
3574
|
function replaceStudioPdfCalloutBlocksInGeneratedLatex(
|
|
3484
3575
|
latex: string,
|
|
3485
3576
|
blocks: StudioPdfMarkdownCalloutBlock[],
|
|
@@ -3494,16 +3585,8 @@ function replaceStudioPdfCalloutBlocksInGeneratedLatex(
|
|
|
3494
3585
|
const endIndex = transformed.indexOf(endMarker, startIndex + startMarker.length);
|
|
3495
3586
|
if (endIndex < 0) continue;
|
|
3496
3587
|
const inner = transformed.slice(startIndex + startMarker.length, endIndex).trim();
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3499
|
-
: block.kind === "tip"
|
|
3500
|
-
? "Tip"
|
|
3501
|
-
: block.kind === "warning"
|
|
3502
|
-
? "Warning"
|
|
3503
|
-
: block.kind === "important"
|
|
3504
|
-
? "Important"
|
|
3505
|
-
: "Caution";
|
|
3506
|
-
const replacement = `\\begin{studiocallout}{${label}}\n${inner}\n\\end{studiocallout}`;
|
|
3588
|
+
const style = getStudioPdfCalloutStyle(block.kind);
|
|
3589
|
+
const replacement = `\\begin{studiocallout}{${style.label}}{${style.borderColor}}{${style.textColor}}{${style.labelBgColor}}\n${inner}\n\\end{studiocallout}`;
|
|
3507
3590
|
transformed = transformed.slice(0, startIndex) + replacement + transformed.slice(endIndex + endMarker.length);
|
|
3508
3591
|
}
|
|
3509
3592
|
return transformed;
|
|
@@ -4033,11 +4116,12 @@ async function renderStudioLiteralTextPdf(text: string, title = "Studio export",
|
|
|
4033
4116
|
\\usepackage[${literalPdfConfig.geometryOptions}]{geometry}
|
|
4034
4117
|
${literalPdfConfig.fontCommands}\\usepackage{fvextra}
|
|
4035
4118
|
\\usepackage{xcolor}
|
|
4119
|
+
\\definecolor{StudioCodeBlockBg}{HTML}{F6F8FA}
|
|
4036
4120
|
\\usepackage{upquote}
|
|
4037
4121
|
\\begin{document}
|
|
4038
4122
|
\\renewcommand{\\baselinestretch}{${literalPdfConfig.lineStretch}}\\selectfont
|
|
4039
4123
|
${literalPdfConfig.fontSizeCommand}\\section*{${title.replace(/[{}\\]/g, "").trim() || "Studio export"}}
|
|
4040
|
-
\\VerbatimInput[breaklines,breakanywhere,fontsize=\\small,frame=single,rulecolor=\\color{black!15},framesep=2mm]{input.txt}
|
|
4124
|
+
\\VerbatimInput[breaklines,breakanywhere,fontsize=\\small,bgcolor=StudioCodeBlockBg,frame=single,rulecolor=\\color{black!15},framesep=2mm]{input.txt}
|
|
4041
4125
|
\\end{document}
|
|
4042
4126
|
`;
|
|
4043
4127
|
|
|
@@ -5337,6 +5421,13 @@ function parseIncomingMessage(data: RawData): IncomingStudioMessage | null {
|
|
|
5337
5421
|
};
|
|
5338
5422
|
}
|
|
5339
5423
|
|
|
5424
|
+
if (msg.type === "refresh_from_disk_request" && typeof msg.requestId === "string") {
|
|
5425
|
+
return {
|
|
5426
|
+
type: "refresh_from_disk_request",
|
|
5427
|
+
requestId: msg.requestId,
|
|
5428
|
+
};
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5340
5431
|
if (msg.type === "send_to_editor_request" && typeof msg.requestId === "string" && typeof msg.content === "string") {
|
|
5341
5432
|
return {
|
|
5342
5433
|
type: "send_to_editor_request",
|
|
@@ -5734,6 +5825,7 @@ ${cssVarsBlock}
|
|
|
5734
5825
|
<div class="controls">
|
|
5735
5826
|
<button id="saveAsBtn" type="button" title="Save editor content to a new file path. Cmd/Ctrl+S falls back here when no direct save path is available.">Save editor as…</button>
|
|
5736
5827
|
<button id="saveOverBtn" type="button" title="Overwrite current file with editor content. Shortcut: Cmd/Ctrl+S.">Save editor</button>
|
|
5828
|
+
<button id="refreshFromDiskBtn" type="button" title="Reload the current file-backed document from disk.">Refresh from disk</button>
|
|
5737
5829
|
<label class="file-label" title="Load a local file into editor text.">Load file content<input id="fileInput" type="file" accept=".md,.markdown,.mdx,.qmd,.js,.mjs,.cjs,.jsx,.ts,.mts,.cts,.tsx,.py,.pyw,.sh,.bash,.zsh,.json,.jsonc,.json5,.rs,.c,.h,.cpp,.cxx,.cc,.hpp,.hxx,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.tex,.latex,.diff,.patch,.java,.go,.rb,.swift,.html,.htm,.css,.xml,.yaml,.yml,.toml,.lua,.txt,.rst,.adoc" /></label>
|
|
5738
5830
|
<button id="loadGitDiffBtn" type="button" title="Load the current git diff from the Studio context into the editor.">Load git diff</button>
|
|
5739
5831
|
<button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
|
|
@@ -7119,6 +7211,50 @@ export default function (pi: ExtensionAPI) {
|
|
|
7119
7211
|
return;
|
|
7120
7212
|
}
|
|
7121
7213
|
|
|
7214
|
+
if (msg.type === "refresh_from_disk_request") {
|
|
7215
|
+
if (!isValidRequestId(msg.requestId)) {
|
|
7216
|
+
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
|
7217
|
+
return;
|
|
7218
|
+
}
|
|
7219
|
+
if (isStudioBusy()) {
|
|
7220
|
+
sendToClient(client, { type: "busy", requestId: msg.requestId, message: "Studio is busy." });
|
|
7221
|
+
return;
|
|
7222
|
+
}
|
|
7223
|
+
if (!initialStudioDocument || !initialStudioDocument.path) {
|
|
7224
|
+
sendToClient(client, {
|
|
7225
|
+
type: "error",
|
|
7226
|
+
requestId: msg.requestId,
|
|
7227
|
+
message: "Refresh from disk is only available for file-backed documents.",
|
|
7228
|
+
});
|
|
7229
|
+
return;
|
|
7230
|
+
}
|
|
7231
|
+
|
|
7232
|
+
const refreshed = readStudioFile(initialStudioDocument.path, studioCwd);
|
|
7233
|
+
if (refreshed.ok === false) {
|
|
7234
|
+
sendToClient(client, {
|
|
7235
|
+
type: "error",
|
|
7236
|
+
requestId: msg.requestId,
|
|
7237
|
+
message: refreshed.message,
|
|
7238
|
+
});
|
|
7239
|
+
return;
|
|
7240
|
+
}
|
|
7241
|
+
|
|
7242
|
+
initialStudioDocument = {
|
|
7243
|
+
text: refreshed.text,
|
|
7244
|
+
label: refreshed.label,
|
|
7245
|
+
source: "file",
|
|
7246
|
+
path: refreshed.resolvedPath,
|
|
7247
|
+
};
|
|
7248
|
+
|
|
7249
|
+
broadcast({
|
|
7250
|
+
type: "studio_document",
|
|
7251
|
+
requestId: msg.requestId,
|
|
7252
|
+
document: initialStudioDocument,
|
|
7253
|
+
message: `Reloaded ${refreshed.label} from disk.`,
|
|
7254
|
+
});
|
|
7255
|
+
return;
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7122
7258
|
if (msg.type === "send_to_editor_request") {
|
|
7123
7259
|
if (!isValidRequestId(msg.requestId)) {
|
|
7124
7260
|
sendToClient(client, { type: "error", requestId: msg.requestId, message: "Invalid request ID." });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.42",
|
|
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",
|