eure-ls 0.2.0

Language Server Protocol implementation for Eure
Documentation
//! Core types for the Eure Language Server.
//!
//! This module contains types shared between native and WASM implementations.

use std::collections::HashSet;

#[cfg(not(target_arch = "wasm32"))]
use eure::query::TextFileContent;
use eure::query::{Glob, TextFile};
use query_flow::RevisionCounter;
use serde_json::Value;

use crate::queries::{LspDiagnostics, LspFileDiagnostics, LspSemanticTokens};

/// Platform-agnostic request ID.
///
/// LSP allows request IDs to be either integers or strings.
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum CoreRequestId {
    Int(i32),
    Str(String),
}

impl CoreRequestId {
    pub fn as_str(&self) -> String {
        match self {
            CoreRequestId::Int(n) => n.to_string(),
            CoreRequestId::Str(s) => s.clone(),
        }
    }
}

impl From<i32> for CoreRequestId {
    fn from(n: i32) -> Self {
        CoreRequestId::Int(n)
    }
}

impl From<String> for CoreRequestId {
    fn from(s: String) -> Self {
        CoreRequestId::Str(s)
    }
}

impl From<&str> for CoreRequestId {
    fn from(s: &str) -> Self {
        CoreRequestId::Str(s.to_string())
    }
}

impl From<&Value> for CoreRequestId {
    fn from(v: &Value) -> Self {
        serde_json::from_value(v.clone()).unwrap()
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl From<lsp_server::RequestId> for CoreRequestId {
    fn from(id: lsp_server::RequestId) -> Self {
        serde_json::from_value(serde_json::to_value(&id).unwrap()).unwrap()
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl From<&CoreRequestId> for lsp_server::RequestId {
    fn from(id: &CoreRequestId) -> Self {
        serde_json::from_value(serde_json::to_value(id).unwrap()).unwrap()
    }
}

/// Effects the core needs the platform to perform.
#[derive(Debug, Clone)]
pub enum Effect {
    /// Request to fetch a file's content.
    FetchFile(TextFile),
    /// Request to expand a glob pattern.
    ExpandGlob {
        /// Unique identifier for this glob request.
        id: String,
        /// The glob pattern to expand.
        glob: Glob,
    },
}

/// LSP error information.
#[derive(Debug, Clone)]
pub struct LspError {
    pub code: i32,
    pub message: String,
}

impl LspError {
    pub fn new(code: i32, message: impl Into<String>) -> Self {
        Self {
            code,
            message: message.into(),
        }
    }

    pub fn internal_error(message: impl Into<String>) -> Self {
        Self::new(-32603, message)
    }

    pub fn invalid_params(message: impl Into<String>) -> Self {
        Self::new(-32602, message)
    }

    pub fn method_not_found(method: &str) -> Self {
        Self::new(-32601, format!("Method not found: {}", method))
    }
}

/// Output messages from the core.
#[derive(Debug, Clone)]
pub enum LspOutput {
    /// Response to a request.
    Response {
        id: CoreRequestId,
        result: Result<Value, LspError>,
    },
    /// Notification to send to client.
    Notification { method: String, params: Value },
}

/// A completion request: document and cursor as a byte offset.
///
/// Not a query on purpose; see `eure::query::get_completions`.
#[derive(Clone, Debug)]
pub struct CompletionRequest {
    pub file: TextFile,
    pub offset: u32,
}

/// A hover request: document and cursor as a byte offset.
///
/// Not a query on purpose; see `eure::query::get_hover`.
#[derive(Clone, Debug)]
pub struct HoverRequest {
    pub file: TextFile,
    pub offset: u32,
}

/// Keep the UTF-16 position until the source is available, including unopened schemas.
#[derive(Clone, Debug)]
pub struct DefinitionRequest {
    pub file: TextFile,
    pub position: lsp_types::Position,
}

/// LSP commands. Each variant carries what is needed to (re-)execute the
/// request once the assets it suspended on are available.
#[derive(Clone)]
pub enum CommandQuery {
    SemanticTokensFull(LspSemanticTokens),
    Completion(CompletionRequest),
    Hover(HoverRequest),
    Definition(DefinitionRequest),
    SchemaContent(TextFile),
}

impl CommandQuery {
    /// The document this command operates on.
    pub fn file(&self) -> &TextFile {
        match self {
            CommandQuery::SemanticTokensFull(q) => &q.file,
            CommandQuery::Completion(q) => &q.file,
            CommandQuery::Hover(q) => &q.file,
            CommandQuery::Definition(q) => &q.file,
            CommandQuery::SchemaContent(file) => file,
        }
    }

    /// Name used in log messages.
    pub fn name(&self) -> &'static str {
        match self {
            CommandQuery::SemanticTokensFull(_) => "SemanticTokens",
            CommandQuery::Completion(_) => "Completion",
            CommandQuery::Hover(_) => "Hover",
            CommandQuery::Definition(_) => "Definition",
            CommandQuery::SchemaContent(_) => "SchemaContent",
        }
    }
}

/// Result of executing a command query.
pub enum CommandResult {
    SemanticTokens(Option<lsp_types::SemanticTokens>),
    Completion(Vec<lsp_types::CompletionItem>),
    Hover(Option<lsp_types::Hover>),
    Definition(Vec<lsp_types::LocationLink>),
    SchemaContent(String),
}

/// A pending LSP request waiting for assets to be resolved.
pub struct PendingRequest {
    /// The request ID.
    pub id: CoreRequestId,
    /// The command to execute.
    pub command: CommandQuery,
    /// Assets this request is waiting for.
    pub waiting_for: HashSet<TextFile>,
}

/// Subscription for diagnostics with revision tracking (legacy per-URI).
#[derive(Clone)]
pub struct DiagnosticsSubscription {
    pub query: LspDiagnostics,
    pub last_revision: RevisionCounter,
}

/// Per-file diagnostics subscription with revision tracking.
///
/// Used for the new per-file polling approach where each file
/// has its own subscription and revision counter.
#[derive(Clone)]
pub struct FileDiagnosticsSubscription {
    pub file: TextFile,
    pub query: LspFileDiagnostics,
    pub last_revision: RevisionCounter,
}

// Native-only types for I/O operations

/// Request to read a file from disk.
#[cfg(not(target_arch = "wasm32"))]
pub struct IoRequest {
    pub file: TextFile,
}

/// Response from reading a file.
#[cfg(not(target_arch = "wasm32"))]
pub struct IoResponse {
    pub file: TextFile,
    /// Content or error from fetching the file.
    pub result: Result<TextFileContent, anyhow::Error>,
}