Archived
Add CodeMirror file viewer highlighting
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { EditorState, type Extension } from "@codemirror/state";
|
||||
import { EditorView, keymap, lineNumbers } from "@codemirror/view";
|
||||
import { defaultKeymap } from "@codemirror/commands";
|
||||
import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
|
||||
import { javascript } from "@codemirror/lang-javascript";
|
||||
import { json } from "@codemirror/lang-json";
|
||||
import { markdown } from "@codemirror/lang-markdown";
|
||||
import { css as cssLang } from "@codemirror/lang-css";
|
||||
import { html as htmlLang } from "@codemirror/lang-html";
|
||||
import { python } from "@codemirror/lang-python";
|
||||
import { rust } from "@codemirror/lang-rust";
|
||||
import { go } from "@codemirror/lang-go";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
|
||||
@customElement("code-viewer")
|
||||
export class CodeViewer extends LitElement {
|
||||
@property() content = "";
|
||||
@property() language: string | undefined;
|
||||
@query(".host") private editorHost?: HTMLDivElement;
|
||||
|
||||
private view: EditorView | undefined;
|
||||
|
||||
override firstUpdated(): void {
|
||||
this.recreateEditor();
|
||||
}
|
||||
|
||||
override updated(changed: Map<string, unknown>): void {
|
||||
if (changed.has("content") || changed.has("language")) this.recreateEditor();
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
this.view?.destroy();
|
||||
this.view = undefined;
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<div class="host"></div>`;
|
||||
}
|
||||
|
||||
private recreateEditor(): void {
|
||||
if (!this.editorHost) return;
|
||||
this.view?.destroy();
|
||||
this.view = new EditorView({
|
||||
parent: this.editorHost,
|
||||
state: EditorState.create({
|
||||
doc: this.content,
|
||||
extensions: [
|
||||
lineNumbers(),
|
||||
keymap.of(defaultKeymap),
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
EditorState.readOnly.of(true),
|
||||
EditorView.editable.of(false),
|
||||
EditorView.lineWrapping,
|
||||
viewerTheme,
|
||||
...languageExtensions(this.language),
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
:host { display: block; min-height: 0; height: 100%; }
|
||||
.host { height: 100%; min-height: 0; overflow: auto; }
|
||||
`;
|
||||
}
|
||||
|
||||
const viewerTheme = EditorView.theme({
|
||||
"&": {
|
||||
height: "100%",
|
||||
color: "#e6edf3",
|
||||
backgroundColor: "#0d1117",
|
||||
fontSize: "12px",
|
||||
},
|
||||
".cm-scroller": {
|
||||
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
||||
lineHeight: "1.45",
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: "#0d1117",
|
||||
color: "#6e7681",
|
||||
borderRight: "1px solid #21262d",
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-activeLine": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: "transparent",
|
||||
},
|
||||
"&.cm-focused": {
|
||||
outline: "none",
|
||||
},
|
||||
});
|
||||
|
||||
function languageExtensions(language: string | undefined): Extension[] {
|
||||
switch (language) {
|
||||
case "typescript": return [javascript({ typescript: true })];
|
||||
case "javascript": return [javascript()];
|
||||
case "json": return [json()];
|
||||
case "markdown": return [markdown()];
|
||||
case "css": return [cssLang()];
|
||||
case "html": return [htmlLang()];
|
||||
case "python": return [python()];
|
||||
case "rust": return [rust()];
|
||||
case "go": return [go()];
|
||||
default: return [];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { LitElement, html, type TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import type { FileContentResponse, FileTreeEntry, GitDiffResponse, GitStatusResponse, Workspace } from "../api";
|
||||
import { workspacePanelStyles } from "./shared";
|
||||
import "./CodeViewer";
|
||||
|
||||
@customElement("workspace-panel")
|
||||
export class WorkspacePanel extends LitElement {
|
||||
@@ -73,7 +74,7 @@ export class WorkspacePanel extends LitElement {
|
||||
if (file.binary) return html`<p class="muted">Binary file: ${file.path}</p>`;
|
||||
return html`
|
||||
<div class="viewer-header"><strong>${file.path}</strong><small>${file.language ?? "text"}${file.truncated ? " · truncated" : ""}</small></div>
|
||||
<pre><code>${file.content}</code></pre>
|
||||
<code-viewer .content=${file.content} .language=${file.language}></code-viewer>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ export class WorkspacePanel extends LitElement {
|
||||
if (!diff) return html`<p class="muted">Loading diff…</p>`;
|
||||
return html`
|
||||
<div class="viewer-header"><strong>${diff.path ?? "diff"}</strong><small>${diff.staged ? "staged" : "unstaged"}${diff.truncated ? " · truncated" : ""}</small></div>
|
||||
<pre><code>${diff.diff || "No unstaged diff."}</code></pre>
|
||||
<code-viewer .content=${diff.diff || "No unstaged diff."}></code-viewer>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,10 @@ export const workspacePanelStyles = css`
|
||||
.row:hover, .row.selected { background: #0d2847; }
|
||||
.row span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.summary { margin: 4px 6px 8px; color: #8b949e; }
|
||||
.viewer { min-height: 0; overflow: auto; }
|
||||
.viewer { min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.viewer-header { position: sticky; top: 0; display: flex; justify-content: space-between; gap: 8px; padding: 8px; border-bottom: 1px solid #21262d; background: #0d1117; }
|
||||
.viewer-header strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
code-viewer { flex: 1 1 auto; min-height: 0; }
|
||||
pre { margin: 0; padding: 10px; overflow: auto; font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
p { margin: 10px; }
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user