Skip to content

Latest commit

Β 

History

History
363 lines (299 loc) Β· 14.7 KB

File metadata and controls

363 lines (299 loc) Β· 14.7 KB

DSCode Architecture (Tauri-Based)

Table of Contents

Overview

DSCode uses a hybrid architecture: Tauri frontend (web-based UI with Monaco) + Rust backend (file ops, Git, LSP management).

Key Decision: Tauri over Pure Rust (egui)

The project initially considered egui (pure Rust UI) but chose Tauri because:

  • Monaco Editor can be used directly (same as VS Code) instead of building an editor from scratch
  • Web-based UI enables pixel-perfect VS Code parity with Svelte components
  • Full Node.js extension compatibility vs ~70-80% with a custom UI

Architecture Layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TAURI APPLICATION                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚   FRONTEND (WebView - Chromium)                    β”‚    β”‚
β”‚  β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚
β”‚  β”‚   β”‚  UI Layer (TypeScript + Svelte)             β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - Activity Bar, Sidebar, Panels, StatusBar  β”‚ β”‚    β”‚
β”‚  β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚
β”‚  β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚
β”‚  β”‚   β”‚  Monaco Editor (from VS Code)                β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - Same editor as VS Code                    β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - All Monaco features built-in              β”‚ β”‚    β”‚
β”‚  β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                   β”‚                                         β”‚
β”‚                   β”‚ Tauri IPC (invoke/emit)                 β”‚
β”‚                   β”‚ - Async commands                        β”‚
β”‚                   β”‚ - Event streams                         β”‚
β”‚                   β”‚                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚   RUST BACKEND                                     β”‚    β”‚
β”‚  β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚
β”‚  β”‚   β”‚  Tauri Command Handlers                      β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - File operations (read, write, watch)      β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - Git operations (status, commit, diff)     β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - Configuration (settings, keybindings)     β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - Command registry                          β”‚ β”‚    β”‚
β”‚  β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚
β”‚  β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚    β”‚
β”‚  β”‚   β”‚  Core Services                               β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - rope (text buffer)                        β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - tree-sitter (syntax)                      β”‚ β”‚    β”‚
β”‚  β”‚   β”‚  - git2 (git integration)                    β”‚ β”‚    β”‚
β”‚  β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                   β”‚                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β”‚ nng IPC (backend-to-backend only)
                    β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                                β”‚              β”‚
β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Extension    β”‚            β”‚  LSP Proxy   β”‚  β”‚   Remote    β”‚
β”‚ Host Process β”‚            β”‚  Process     β”‚  β”‚   Agent     β”‚
β”‚              β”‚            β”‚              β”‚  β”‚             β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚            β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”‚ File Ops    β”‚
β”‚ β”‚ Node.js  β”‚ β”‚            β”‚ β”‚ rust-    β”‚ β”‚  β”‚ Terminal    β”‚
β”‚ β”‚ Runtime  β”‚ β”‚            β”‚ β”‚ analyzer β”‚ β”‚  β”‚ Port Fwd    β”‚
β”‚ β”‚          β”‚ β”‚            β”‚ β”‚ ts-serverβ”‚ β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ vscode.* β”‚ β”‚            β”‚ β”‚ ...      β”‚ β”‚
β”‚ β”‚ API      β”‚ β”‚            β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚          β”‚ β”‚            β”‚              β”‚
β”‚ β”‚ Electron β”‚ β”‚            β”‚ JSON-RPC ↔   β”‚
β”‚ β”‚ Shims    β”‚ β”‚            β”‚ nng Bridge   β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why Tauri?

Tauri vs Electron vs Pure Rust (egui)

Aspect Electron (VS Code) egui (Pure Rust) Tauri (Our Choice)
Editor Monaco βœ… Build from scratch ❌ Monaco βœ…
UI Complexity Web (easy) βœ… Custom widgets (hard) ❌ Web (easy) βœ…
Extension Compat 100% βœ… 70-80% ⚠️ 95%+ βœ…
Bundle Size 200MB+ ❌ 10MB βœ… 50MB βœ…
Memory 350MB ❌ 150MB βœ… 140MB βœ…
Startup 2.5s ⚠️ 0.8s βœ… 0.6s βœ…
Backend Node.js ⚠️ Rust βœ… Rust βœ…
Risk Level Low βœ… HIGH ❌ Low βœ…

Decision: Tauri gives us the best of all worlds!

IPC Strategy

Two IPC Systems

1. Tauri IPC (Frontend ↔ Rust Backend)

// Rust side: Define commands
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    tokio::fs::read_to_string(path)
        .await
        .map_err(|e| e.to_string())
}

#[tauri::command]
async fn git_status(repo_path: String) -> Result<Vec<GitChange>, String> {
    let repo = Repository::open(repo_path)?;
    // ... git operations
    Ok(changes)
}

// Frontend side: Invoke commands
import { invoke } from '@tauri-apps/api/tauri';

const content = await invoke('read_file', { path: '/path/to/file' });
const changes = await invoke('git_status', { repoPath: '/path/to/repo' });

2. nng IPC (Rust Backend ↔ Extension Host/LSP/Remote)

// Extension Host communication (nng)
let msg = IPCMessage::ExtensionAPICall {
    method: "workspace.openTextDocument",
    args: vec![uri],
};

let response = nng_client.send(serde_json::to_string(&msg)?).await?;

Why Two IPC Systems?

  • Tauri IPC: Frontend <-> Backend (async/await, type-safe, built-in)
  • NNG IPC: Backend services (high-performance, multiple patterns, serde_json serialization)

Extension System

Node.js Runtime (Full Compatibility)

Extension Host Process (separate)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Node.js v18+                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  vscode.* API Implementation  β”‚  β”‚
β”‚  β”‚  - window, workspace, etc.    β”‚  β”‚
β”‚  β”‚                               β”‚  β”‚
β”‚  β”‚  Electron API Shims           β”‚  β”‚
β”‚  β”‚  - electron.remote.app        β”‚  β”‚
β”‚  β”‚  - electron.ipcRenderer       β”‚  β”‚
β”‚  β”‚                               β”‚  β”‚
β”‚  β”‚  Extension Loader             β”‚  β”‚
β”‚  β”‚  - Load .vsix                 β”‚  β”‚
β”‚  β”‚  - Execute activate()         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Loaded Extensions            β”‚  β”‚
β”‚  β”‚  - ESLint βœ…                  β”‚  β”‚
β”‚  β”‚  - Prettier βœ…                β”‚  β”‚
β”‚  β”‚  - Python (with native) βœ…    β”‚  β”‚
β”‚  β”‚  - GitLens βœ…                 β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ nng IPC
         β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
         β”‚ Rust       β”‚
         β”‚ Backend    β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Electron API Shims

// electron-shim/index.ts
// Provide compatibility for extensions using Electron APIs

export const remote = {
  app: {
    getPath(name: string): string {
      // Map to Tauri's path API
      return invoke('get_app_path', { name });
    },
    getVersion(): string {
      return invoke('get_app_version');
    },
  },
  dialog: {
    showOpenDialog(options: any): Promise<any> {
      return invoke('show_open_dialog', { options });
    },
  },
};

export const ipcRenderer = {
  send(channel: string, ...args: any[]): void {
    invoke('electron_ipc_send', { channel, args });
  },
  on(channel: string, listener: Function): void {
    // Subscribe to events from Rust
    listen(`electron:${channel}`, listener);
  },
};

Monaco Editor Integration

Using Monaco Directly

<!-- src/components/EditorArea.svelte -->
<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import * as monaco from 'monaco-editor';
  import { invoke } from '@tauri-apps/api/core';

  let container: HTMLDivElement;
  let editor: monaco.editor.IStandaloneCodeEditor;

  onMount(() => {
    editor = monaco.editor.create(container, {
      value: initialContent,
      language: 'typescript',
      theme: 'vs-dark',
      minimap: { enabled: true },
      lineNumbers: 'on',
      glyphMargin: true,
    });

    editor.onDidChangeModelContent(async (e) => {
      await invoke('text_document_changed', {
        uri: currentFile,
        changes: e.changes,
      });
    });
  });

  onDestroy(() => {
    editor?.dispose();
  });
</script>

<div bind:this={container} class="editor-container" />

Monaco Extensions Work!

  • Vim mode (via monaco-vim) βœ…
  • Emacs bindings βœ…
  • Bracket matching βœ…
  • Multi-cursor βœ…
  • IntelliSense βœ…
  • All VS Code editor features βœ…

Performance Model

Memory Comparison

VS Code (Electron):
β”œβ”€β”€ Chromium:     150 MB
β”œβ”€β”€ Node.js:      80 MB
β”œβ”€β”€ Extensions:   100 MB
└── Total:        ~350 MB

DSCode (Tauri):
β”œβ”€β”€ WebView:      80 MB (system WebView on macOS/Linux)
β”œβ”€β”€ Rust Backend: 30 MB
β”œβ”€β”€ Extensions:   40 MB (separate process)
└── Total:        ~140 MB (60% reduction!)

Startup Time

VS Code (Electron):
1. Launch Electron:        800ms
2. Initialize Node.js:     600ms
3. Load UI:               800ms
4. Activate extensions:   300ms
Total:                    2.5s

DSCode (Tauri):
1. Launch Tauri:          200ms
2. Load WebView:          200ms
3. Initialize Rust:       100ms
4. Activate extensions:   100ms
Total:                    0.6s (4x faster!)

File Operations (Rust-Powered)

use tokio::fs;
use notify::{Watcher, RecursiveMode};

#[tauri::command]
async fn watch_files(path: String) -> Result<(), String> {
    let (tx, mut rx) = tokio::sync::mpsc::channel(100);

    let mut watcher = notify::recommended_watcher(move |res| {
        tx.blocking_send(res).ok();
    }).unwrap();

    watcher.watch(Path::new(&path), RecursiveMode::Recursive)?;

    tokio::spawn(async move {
        while let Some(event) = rx.recv().await {
            // Emit event to frontend
            app_handle.emit_all("file-changed", event).ok();
        }
    });

    Ok(())
}

Success Criteria (Updated)

v1.0 Goals (All Achievable with Tauri!)

  • βœ… UI Parity: 99% (Monaco + web UI)
  • βœ… Extension Compat: 95%+ (Node.js + Electron shims)
  • βœ… Startup Time: <0.6s (Tauri performance)
  • βœ… Memory: <150MB (60% less than Electron)
  • βœ… Bundle Size: <60MB (vs 200MB for Electron)
  • βœ… Cross-platform: Linux, macOS, Windows (Tauri built-in)
  • βœ… Theme Compat: 100% (Monaco theming)

Risk Assessment

Component Risk Level
Text Editor LOW (Monaco)
UI Layout LOW (Svelte + Web)
Extensions LOW (Node.js)
Webviews LOW (Tauri)
Overall LOW

Conclusion

Tauri architecture = Low risk, high reward!

  • Get Monaco editor (same as VS Code) βœ…
  • Get 95%+ extension compatibility βœ…
  • Get better performance than Electron βœ…
  • Get smaller bundle size βœ…
  • Keep Rust backend benefits βœ