Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions studio/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<span class="pick">
<button id="run" type="button" class="run" title="Run the analyzer on the input file (F5)" disabled>Run</button>
<span id="run-on" class="muted"></span>
<button id="text-toggle" type="button" aria-pressed="true" title="Show or hide the text a run reads, and the list to choose it from">Text</button>
<label class="check" title="Run the engine in debug mode (-DEV): it keeps the parse tree after every pass, and each pass in the sequence then offers that tree and what its rules matched. Slower, and the trees can be large.">
<input id="debug" type="checkbox" /> Debug
</label>
Expand Down Expand Up @@ -48,6 +49,13 @@
<div class="results-body"></div>
</section>
</section>
<section id="text-pane" class="text-pane" aria-label="The text a run reads">
<div class="text-bar">
<label for="text-pick" class="muted">Text</label>
<select id="text-pick" title="The text the next run reads"></select>
</div>
<div id="text-editor" class="editor"></div>
</section>
</main>
</div>
<dialog id="github-dialog" class="github-dialog" aria-labelledby="github-title">
Expand Down
83 changes: 83 additions & 0 deletions studio/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export const RUN_MARKERS = "nlp++ run";
const SAVE_AFTER_MS = 500;
// Whether Debug was ticked last time, in this browser.
const DEBUG_KEY = "nlp-studio.debug";
// Whether the text pane was hidden last time, in this browser.
const TEXT_KEY = "nlp-studio.text-pane";

function remembered(key: string): string | null {
try {
Expand All @@ -74,6 +76,13 @@ function listedTree(file: TreeFile): ListedTree {
return { name: file.name, pass: file.pass, passName: file.passName, bytes: file.size };
}

// Input files in the order the text list shows them: the input folder's own files first,
// then each folder's.
function byFolder(a: string, b: string): number {
const depth = (p: string) => (p.split("/").length > 2 ? 1 : 0);
return depth(a) - depth(b) || a.localeCompare(b, undefined, { numeric: true });
}

function darkTheme(): boolean {
const set = document.documentElement.dataset.theme;
return set ? set === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches;
Expand Down Expand Up @@ -103,6 +112,10 @@ export class Studio {
passList: Pass[] = [];
account: Account | null = null;
readonly editor: monaco.editor.IStandaloneCodeEditor;
// The text a run reads, beside the editor: the same model as the input file, so an
// edit in either is the other's too, and is kept, reverted and downloaded the same.
readonly textEditor: monaco.editor.IStandaloneCodeEditor;
private textChoices = "";
readonly panel: RunPanel;
readonly drafts = DraftStore.browser();
private readonly models = new Map<string, monaco.editor.ITextModel>();
Expand Down Expand Up @@ -153,6 +166,34 @@ export class Studio {
});
monaco.editor.onDidChangeMarkers(() => this.showProblems());

this.textEditor = monaco.editor.create(byId("text-editor"), {
automaticLayout: true,
fontSize: 13,
wordWrap: "on",
minimap: { enabled: false },
scrollBeyondLastLine: false,
lineNumbersMinChars: 3,
theme: darkTheme() ? THEMES.dark : THEMES.light,
});
this.textEditor.addAction({
id: "nlp.runAnalyzer",
label: "Run Analyzer",
keybindings: [monaco.KeyCode.F5],
run: () => void this.run(),
});
byId<HTMLSelectElement>("text-pick").addEventListener("change", (event) => {
this.inputPath = (event.target as HTMLSelectElement).value || undefined;
this.showRunTarget();
});
const toggle = byId("text-toggle");
toggle.setAttribute("aria-pressed", remembered(TEXT_KEY) === "hidden" ? "false" : "true");
toggle.addEventListener("click", () => {
const shown = toggle.getAttribute("aria-pressed") !== "true";
toggle.setAttribute("aria-pressed", String(shown));
rememberSetting(TEXT_KEY, shown ? "shown" : "hidden");
this.showText();
});

this.panel = new RunPanel(byId("results"), {
open: (path, at) => this.openPath(path, at),
openTree: (name) => void this.openTree(name),
Expand Down Expand Up @@ -929,6 +970,48 @@ export class Studio {
run.disabled = this.running || !name;
run.textContent = this.running ? "Running…" : "Run";
run.classList.toggle("busy", this.running);
this.showText();
}

// The text pane: every input file to choose from, grouped by folder, and the chosen one
// to read and edit. Shown while the analyzer has input and the Text button is pressed.
private showText(): void {
const inputs = [...this.models.keys()].filter((p) => p.startsWith("input/")).sort(byFolder);
const shown = inputs.length > 0 && byId("text-toggle").getAttribute("aria-pressed") === "true";
byId("text-pane").hidden = !shown;
byId("text-toggle").hidden = inputs.length === 0;
document.querySelector(".layout")?.classList.toggle("with-text", shown);

const pick = byId<HTMLSelectElement>("text-pick");
const choices = inputs.join("\n");
if (choices !== this.textChoices) {
this.textChoices = choices;
pick.replaceChildren();
const groups = new Map<string, HTMLElement>();
for (const path of inputs) {
const rel = path.slice("input/".length);
const cut = rel.lastIndexOf("/");
const folder = cut < 0 ? "" : rel.slice(0, cut);
const option = new Option(cut < 0 ? rel : rel.slice(cut + 1), path);
if (this.handed.has(path)) option.textContent += " (handed in)";
if (!folder) {
pick.append(option);
continue;
}
let group = groups.get(folder);
if (!group) {
group = document.createElement("optgroup");
(group as HTMLOptGroupElement).label = folder;
groups.set(folder, group);
pick.append(group);
}
group.append(option);
}
}
pick.value = this.inputPath ?? "";
pick.disabled = this.running;
const model = this.inputPath ? this.models.get(this.inputPath) ?? null : null;
if (this.textEditor.getModel() !== model) this.textEditor.setModel(model);
}

// ---- The file list ---------------------------------------------------------------
Expand Down
13 changes: 12 additions & 1 deletion studio/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
#analyzer { max-width: min(280px, 40vw); text-overflow: ellipsis; }

.layout { flex: 1; display: grid; grid-template-columns: minmax(200px, 280px) 1fr; min-height: 0; }
@media (max-width: 720px) { .layout { grid-template-columns: 1fr; grid-template-rows: 30vh 1fr; } }
/* The text a run reads, beside the editor, while there is one to show. */
.layout.with-text { grid-template-columns: minmax(200px, 280px) minmax(0, 1.4fr) minmax(260px, 1fr); }
@media (max-width: 720px) {
.layout, .layout.with-text { grid-template-columns: 1fr; grid-template-rows: 30vh 1fr; }
.layout.with-text { grid-template-rows: 30vh 1fr 40vh; }
}

.files { overflow: auto; padding: 8px 10px; background: var(--panel); border-right: 1px solid var(--line); }
/* The lists are @visualtext/analyzer-views (its style.css), in the studio's colours. */
Expand Down Expand Up @@ -48,6 +53,12 @@ button.commit { border-color: var(--accent); color: var(--on-ink); font-weight:
.editor-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.path { padding: 5px 12px; font-size: 12px; color: var(--muted); border-bottom: 1px solid var(--line); background: var(--panel); }
.editor { flex: 1; min-height: 0; }
.text-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; border-left: 1px solid var(--line); }
.text-pane[hidden] { display: none; }
.text-bar { display: flex; align-items: center; gap: 8px; padding: 3px 8px 3px 12px; border-bottom: 1px solid var(--line);
background: var(--panel); font-size: 12px; }
.text-bar select { flex: 1; min-width: 0; }
#text-toggle[aria-pressed="true"] { background: var(--on); color: var(--on-ink); }
.problems { max-height: 22vh; overflow: auto; border-top: 1px solid var(--line); background: var(--panel);
display: flex; flex-direction: column; }
.problems button { border: 0; border-radius: 0; text-align: left; padding: 4px 12px; background: none; font-size: 12.5px; }
Expand Down
Loading