pub mod capabilities;
pub mod code_actions;
pub mod completion;
pub mod diagnostics;
pub mod formatting;
pub mod hover;
pub mod protocol;
pub mod server;
pub mod text_document;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub use server::LspServer;
pub struct LspState {
pub documents: Arc<RwLock<HashMap<String, TextDocument>>>,
pub voices: Arc<RwLock<Vec<VoiceInfo>>>,
pub capabilities: ServerCapabilities,
}
impl LspState {
pub fn new() -> Self {
Self {
documents: Arc::new(RwLock::new(HashMap::new())),
voices: Arc::new(RwLock::new(Vec::new())),
capabilities: ServerCapabilities::default(),
}
}
pub async fn open_document(&self, uri: String, text: String, language_id: String) {
let document = TextDocument::new(uri.clone(), text, language_id);
self.documents.write().await.insert(uri, document);
}
pub async fn update_document(&self, uri: &str, text: String, version: i32) {
if let Some(doc) = self.documents.write().await.get_mut(uri) {
doc.update(text, version);
}
}
pub async fn close_document(&self, uri: &str) {
self.documents.write().await.remove(uri);
}
pub async fn get_document(&self, uri: &str) -> Option<TextDocument> {
self.documents.read().await.get(uri).cloned()
}
}
impl Default for LspState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TextDocument {
pub uri: String,
pub text: String,
pub language_id: String,
pub version: i32,
}
impl TextDocument {
pub fn new(uri: String, text: String, language_id: String) -> Self {
Self {
uri,
text,
language_id,
version: 1,
}
}
pub fn update(&mut self, text: String, version: i32) {
self.text = text;
self.version = version;
}
pub fn line_count(&self) -> usize {
self.text.lines().count()
}
pub fn get_line(&self, line: usize) -> Option<&str> {
self.text.lines().nth(line)
}
pub fn get_char_at(&self, line: usize, character: usize) -> Option<char> {
self.get_line(line)?.chars().nth(character)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceInfo {
pub id: String,
pub name: String,
pub language: String,
pub description: String,
pub features: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCapabilities {
pub text_document_sync: TextDocumentSyncKind,
pub completion_provider: bool,
pub hover_provider: bool,
pub diagnostic_provider: bool,
pub code_action_provider: bool,
}
impl Default for ServerCapabilities {
fn default() -> Self {
Self {
text_document_sync: TextDocumentSyncKind::Full,
completion_provider: true,
hover_provider: true,
diagnostic_provider: true,
code_action_provider: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TextDocumentSyncKind {
None,
Full,
Incremental,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Position {
pub line: u32,
pub character: u32,
}
impl Position {
pub fn new(line: u32, character: u32) -> Self {
Self { line, character }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Range {
pub start: Position,
pub end: Position,
}
impl Range {
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
pub fn single_line(line: u32, start_char: u32, end_char: u32) -> Self {
Self {
start: Position::new(line, start_char),
end: Position::new(line, end_char),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_lsp_state_creation() {
let state = LspState::new();
assert!(state.documents.read().await.is_empty());
}
#[tokio::test]
async fn test_open_document() {
let state = LspState::new();
state
.open_document(
"file:///test.ssml".to_string(),
"<speak>Hello</speak>".to_string(),
"ssml".to_string(),
)
.await;
let docs = state.documents.read().await;
assert_eq!(docs.len(), 1);
assert!(docs.contains_key("file:///test.ssml"));
}
#[tokio::test]
async fn test_update_document() {
let state = LspState::new();
state
.open_document(
"file:///test.ssml".to_string(),
"<speak>Hello</speak>".to_string(),
"ssml".to_string(),
)
.await;
state
.update_document("file:///test.ssml", "<speak>World</speak>".to_string(), 2)
.await;
let doc = state.get_document("file:///test.ssml").await.unwrap();
assert_eq!(doc.text, "<speak>World</speak>");
assert_eq!(doc.version, 2);
}
#[tokio::test]
async fn test_close_document() {
let state = LspState::new();
state
.open_document(
"file:///test.ssml".to_string(),
"<speak>Hello</speak>".to_string(),
"ssml".to_string(),
)
.await;
state.close_document("file:///test.ssml").await;
assert!(state.get_document("file:///test.ssml").await.is_none());
}
#[test]
fn test_text_document_line_count() {
let doc = TextDocument::new(
"file:///test.txt".to_string(),
"line 1\nline 2\nline 3".to_string(),
"text".to_string(),
);
assert_eq!(doc.line_count(), 3);
}
#[test]
fn test_position_creation() {
let pos = Position::new(5, 10);
assert_eq!(pos.line, 5);
assert_eq!(pos.character, 10);
}
#[test]
fn test_range_creation() {
let range = Range::single_line(3, 5, 15);
assert_eq!(range.start.line, 3);
assert_eq!(range.start.character, 5);
assert_eq!(range.end.line, 3);
assert_eq!(range.end.character, 15);
}
#[test]
fn test_server_capabilities_default() {
let caps = ServerCapabilities::default();
assert!(caps.completion_provider);
assert!(caps.hover_provider);
assert!(caps.diagnostic_provider);
assert_eq!(caps.text_document_sync, TextDocumentSyncKind::Full);
}
}