mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Multi-file refactorings could drop LSP document versions, overwrite content changed after a preview, or leave earlier files saved after a later write failed. Workspace edits now keep server versions and preview fingerprints, are validated on a staging copy, snapshot disk and open buffers, and roll back on commit failure (retaining recovery files if rollback fails). The whole edit is one Undo/Redo step in editors and in the notification. Version reads and transaction work run off the async workers. Closes #461. Co-authored-by: shohruh-genius <7658451+shohruh-genius@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
266 lines
8.0 KiB
Rust
266 lines
8.0 KiB
Rust
//! The backend → UI event bus.
|
||
//!
|
||
//! Feature crates emit through an [`EventSink`]; `src-tauri` implements it by
|
||
//! forwarding to the webview as the single `ide://event` Tauri event. High
|
||
//! volume streams (terminal bytes, search hits, process output) do *not* use
|
||
//! this bus; they use per-request `tauri::ipc::Channel`s.
|
||
|
||
use crate::{DebugSessionId, Diagnostic, DocId, RunId, ServerId, TaskId, TerminalId, TextChange};
|
||
use serde::{Deserialize, Serialize};
|
||
use specta::Type;
|
||
use std::sync::Arc;
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
|
||
#[serde(tag = "type", rename_all = "camelCase", rename_all_fields = "camelCase")]
|
||
pub enum IdeEvent {
|
||
/// Project window membership or selection changed.
|
||
WindowGroupsChanged,
|
||
ProjectTrustChanged {
|
||
root: crate::IpcPath,
|
||
trusted: bool,
|
||
},
|
||
/// Files or directories changed on disk (after debouncing, ignore rules applied).
|
||
FsChanged {
|
||
created: Vec<crate::IpcPath>,
|
||
modified: Vec<crate::IpcPath>,
|
||
removed: Vec<crate::IpcPath>,
|
||
},
|
||
/// Excluded or ignored folders whose contents changed; they stay out of `FsChanged`.
|
||
ExcludedDirsChanged {
|
||
dirs: Vec<crate::IpcPath>,
|
||
},
|
||
/// The backend changed a document (reload from disk, formatting, refactoring).
|
||
/// The UI applies `changes` on top of `base_version`, yielding `version`.
|
||
DocPatched {
|
||
doc: DocId,
|
||
base_version: u32,
|
||
version: u32,
|
||
changes: Vec<TextChange>,
|
||
/// Part of a grouped transaction; the editor must not record a file-local undo.
|
||
#[serde(default)]
|
||
#[specta(optional)]
|
||
workspace_edit: bool,
|
||
},
|
||
/// A document's dirty flag changed (saved / modified externally).
|
||
DocDirty {
|
||
doc: DocId,
|
||
dirty: bool,
|
||
},
|
||
/// A complete workspace edit can be undone as one operation.
|
||
WorkspaceEditApplied {
|
||
id: u32,
|
||
paths: Vec<crate::IpcPath>,
|
||
},
|
||
/// An open document's language changed (Settings › File Types, Override File
|
||
/// Type), or its UI mode (a framework's root marker appeared or went).
|
||
DocLanguage {
|
||
doc: DocId,
|
||
language: crate::LanguageId,
|
||
/// See `DocumentInfo::ui_mode`.
|
||
ui_mode: Option<String>,
|
||
},
|
||
/// Full replacement of diagnostics for one file from one producer.
|
||
Diagnostics {
|
||
path: crate::IpcPath,
|
||
source: String,
|
||
items: Vec<Diagnostic>,
|
||
},
|
||
/// Progress of a background task; `fraction` is `None` when indeterminate.
|
||
Task {
|
||
id: TaskId,
|
||
title: String,
|
||
message: Option<String>,
|
||
fraction: Option<f32>,
|
||
done: bool,
|
||
/// The UI may stop it with `cancel_task(id)` (see [`crate::task`]).
|
||
cancellable: bool,
|
||
},
|
||
/// Project indexing state changed (dumb mode on/off).
|
||
IndexState {
|
||
indexing: bool,
|
||
files: u32,
|
||
symbols: u32,
|
||
},
|
||
/// Git status / branch information should be refreshed.
|
||
VcsChanged {
|
||
root: crate::IpcPath,
|
||
},
|
||
LanguageServer {
|
||
server: ServerId,
|
||
name: String,
|
||
status: ServerStatus,
|
||
message: Option<String>,
|
||
},
|
||
/// A running server lacks a capability; the UI offers the managed tool as a replacement.
|
||
LanguageServerWarning {
|
||
name: String,
|
||
message: String,
|
||
install_tool: String,
|
||
},
|
||
/// A stopped language server left the project's list (disabled in settings, or a
|
||
/// fallback that a preferred server took over from again).
|
||
LanguageServerRemoved {
|
||
server: ServerId,
|
||
},
|
||
/// A language server asked clients to re-request one kind of data for its
|
||
/// open documents (`workspace/inlayHint/refresh`, …), e.g. after indexing.
|
||
LspRefresh {
|
||
server: ServerId,
|
||
kind: LspRefreshKind,
|
||
},
|
||
TerminalExited {
|
||
terminal: TerminalId,
|
||
code: Option<i32>,
|
||
},
|
||
RunExited {
|
||
run: RunId,
|
||
code: Option<i32>,
|
||
},
|
||
Debug {
|
||
session: DebugSessionId,
|
||
event: DebugEvent,
|
||
},
|
||
Notify {
|
||
level: NotifyLevel,
|
||
title: String,
|
||
body: Option<String>,
|
||
},
|
||
/// A setting was written (any window); the windows it concerns (all for a
|
||
/// global value, the project's for a project value) re-read their effective settings.
|
||
SettingsChanged {
|
||
key: String,
|
||
},
|
||
/// The project's source / test / resource / excluded roots changed (build files
|
||
/// changed or folders were marked); the UI re-reads them with `project_roots`.
|
||
ProjectRootsChanged {
|
||
root: crate::IpcPath,
|
||
},
|
||
/// A child process (`ssh`, `git`) asks for a credential; answered with `ssh_askpass_answer`.
|
||
Askpass {
|
||
id: u64,
|
||
prompt: String,
|
||
kind: AskpassKind,
|
||
/// The dialog offers "Remember" for the answer (git HTTPS passwords).
|
||
remember: bool,
|
||
},
|
||
/// Prompt `id` was answered (in any window) or gave up.
|
||
AskpassDone {
|
||
id: u64,
|
||
},
|
||
/// The kernel of an open notebook changed state (`starting`, `idle`, `busy`,
|
||
/// `dead`); `message` says why it failed to start or died.
|
||
NotebookKernel {
|
||
/// Backend document incarnation; stale queued events cannot target a reopen.
|
||
id: String,
|
||
path: crate::IpcPath,
|
||
state: String,
|
||
message: Option<String>,
|
||
},
|
||
}
|
||
|
||
/// What an askpass prompt asks for.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum AskpassKind {
|
||
/// Password or passphrase: hidden input.
|
||
Secret,
|
||
/// User name: visible input.
|
||
Text,
|
||
/// Yes / no (an unknown host key); answered with `yes` or `no`.
|
||
Confirm,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum ServerStatus {
|
||
Starting,
|
||
Ready,
|
||
Busy,
|
||
Crashed,
|
||
Stopped,
|
||
Missing,
|
||
/// Not started: its configuration can't launch it (e.g. no JDK new enough);
|
||
/// the message says what to change, and a settings change retries.
|
||
Misconfigured,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum LspRefreshKind {
|
||
InlayHints,
|
||
SemanticTokens,
|
||
CodeLens,
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub enum NotifyLevel {
|
||
Info,
|
||
Warning,
|
||
Error,
|
||
}
|
||
|
||
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
|
||
#[serde(tag = "kind", rename_all = "camelCase", rename_all_fields = "camelCase")]
|
||
pub enum DebugEvent {
|
||
/// First event of every session (only build output of a root session's
|
||
/// `cargo` step can precede it). `parent` is set for adapter-started child sessions.
|
||
Started {
|
||
name: String,
|
||
adapter: String,
|
||
parent: Option<DebugSessionId>,
|
||
},
|
||
Initialized,
|
||
Stopped {
|
||
thread_id: i64,
|
||
reason: String,
|
||
description: Option<String>,
|
||
all_threads_stopped: bool,
|
||
},
|
||
Continued {
|
||
thread_id: i64,
|
||
all_threads_continued: bool,
|
||
},
|
||
Output {
|
||
category: String,
|
||
output: String,
|
||
},
|
||
Thread {
|
||
thread_id: i64,
|
||
started: bool,
|
||
},
|
||
BreakpointsChanged {
|
||
path: crate::IpcPath,
|
||
},
|
||
Terminated,
|
||
Exited {
|
||
code: i32,
|
||
},
|
||
/// java-debug's `hotcodereplace` event: `change_type` is `BUILD_COMPLETE` (classes
|
||
/// changed and can be reloaded), `STARTING`, `END`, `WARNING` or `ERROR`.
|
||
HotCodeReplace {
|
||
change_type: String,
|
||
message: String,
|
||
},
|
||
}
|
||
|
||
/// Where backend crates publish [`IdeEvent`]s. Cheap to clone via `Arc`.
|
||
pub trait EventSink: Send + Sync + 'static {
|
||
fn emit(&self, event: IdeEvent);
|
||
}
|
||
|
||
impl<F: Fn(IdeEvent) + Send + Sync + 'static> EventSink for F {
|
||
fn emit(&self, event: IdeEvent) {
|
||
self(event)
|
||
}
|
||
}
|
||
|
||
pub type SharedSink = Arc<dyn EventSink>;
|
||
|
||
/// Discards events; for tests and headless tools.
|
||
pub struct NullSink;
|
||
|
||
impl EventSink for NullSink {
|
||
fn emit(&self, _: IdeEvent) {}
|
||
}
|