feat: add built-in file editor with syntax highlighting (#79)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m4s
CI / Build Linux (push) Has been cancelled
CI / Lint & Test (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled

## Summary
- Add CodeMirror 6 editor with syntax highlighting for 40+ languages
- Add file browser sidebar with collapsible directory tree navigation
- Add multi-tab support with dirty state indicators and close buttons
- Add keyboard shortcuts (Ctrl+E toggle, Ctrl+B file browser, Ctrl+S save, Ctrl+W close tab)
- Add editor toggle button to status bar (disabled when not connected)
- Editor automatically uses current session's working directory
- Add Tauri backend commands for file operations (list_directory, read_file_content, write_file_content)

## Test Plan
- [ ] Connect to a session and verify the editor toggle button becomes enabled
- [ ] Press Ctrl+E to open the editor and verify file tree shows the session's CWD
- [ ] Navigate directories and open files to verify syntax highlighting works
- [ ] Edit a file and verify the dirty indicator (*) appears
- [ ] Save with Ctrl+S and verify the dirty indicator disappears
- [ ] Open multiple files and verify tab switching works
- [ ] Close tabs with Ctrl+W or the X button
- [ ] Disconnect and verify the editor automatically closes
- [ ] Verify keyboard shortcuts are documented in the shortcuts modal

Closes #72

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #79
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #79.
This commit is contained in:
2026-01-28 18:20:02 -08:00
committed by Naomi Carrigan
parent edc863e020
commit e45a1a1c98
21 changed files with 3803 additions and 4 deletions
+29
View File
@@ -30,6 +30,7 @@
import SnippetLibraryPanel from "$lib/components/SnippetLibraryPanel.svelte";
import QuickActionsPanel from "$lib/components/QuickActionsPanel.svelte";
import ClipboardHistoryPanel from "$lib/components/ClipboardHistoryPanel.svelte";
import TextInputContextMenu from "$lib/components/TextInputContextMenu.svelte";
import type { Attachment } from "$lib/types/messages";
const INPUT_HISTORY_KEY = "hikari-input-history";
@@ -49,6 +50,23 @@
let showClipboardHistory = $state(false);
let streamerModeActive = $state(false);
// Context menu state
let textareaElement: HTMLTextAreaElement | undefined = $state();
let contextMenuShow = $state(false);
let contextMenuX = $state(0);
let contextMenuY = $state(0);
function handleContextMenu(event: MouseEvent) {
event.preventDefault();
contextMenuShow = true;
contextMenuX = event.clientX;
contextMenuY = event.clientY;
}
function closeContextMenu() {
contextMenuShow = false;
}
isStreamerMode.subscribe((value) => {
streamerModeActive = value;
});
@@ -876,10 +894,12 @@ User: ${formattedMessage}`;
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="resize-handle" onmousedown={handleResizeStart} title="Drag to resize"></div>
<textarea
bind:this={textareaElement}
bind:value={inputValue}
onkeydown={handleKeyDown}
oninput={handleInputChange}
onpaste={handlePaste}
oncontextmenu={handleContextMenu}
placeholder={isConnected
? "Ask Hikari anything... (type / for commands)"
: "Connect to Claude first..."}
@@ -958,6 +978,15 @@ User: ${formattedMessage}`;
/>
{/if}
{#if contextMenuShow && textareaElement}
<TextInputContextMenu
x={contextMenuX}
y={contextMenuY}
inputElement={textareaElement}
onClose={closeContextMenu}
/>
{/if}
<style>
.input-bar {
display: flex;