From 16cc70ccf514c6978d7abc1aa59e641374a60dba Mon Sep 17 00:00:00 2001 From: DavidLee18 Date: Sun, 15 Sep 2024 10:36:55 +0900 Subject: [PATCH] basic sketch 2 --- Cargo.toml | 17 +++++ src/main.rs | 150 ++++++++++++++++++++++++++++++++++++++++++ src/norminette_msg.rs | 71 ++++++++++++++++++++ src/parser.rs | 61 +++++++++++++++++ 4 files changed, 299 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/norminette_msg.rs create mode 100644 src/parser.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5610248 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "norminette_lsp" +version = "0.1.0" +edition = "2021" + +[dependencies] +lsp-types = "0.97.0" +nom = "7.1.3" +serde_json = "1.0.128" +tokio = { version = "1.40.0", features = [ + "rt-multi-thread", + "macros", + "io-std", +] } +tokio-macros = "2.4.0" +tower-lsp = "0.20.0" +tracing-subscriber = "0.3.18" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c2b91c0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,150 @@ +pub mod norminette_msg; +pub mod parser; + +use std::io; +use std::path::Path; +use std::sync::Arc; + +use parser::parse_norminette; +use serde_json::Value; +use tokio::sync::Mutex; +use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::*; +use tower_lsp::{Client, LanguageServer, LspService, Server}; + +#[derive(Debug)] +struct Backend { + client: Client, + // norminette_options: Arc> +} + +#[tower_lsp::async_trait] +impl LanguageServer for Backend { + async fn initialize(&self, _: InitializeParams) -> Result { + Ok(InitializeResult { + server_info: None, + capabilities: ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::INCREMENTAL, + )), + completion_provider: Some(CompletionOptions { + resolve_provider: Some(false), + trigger_characters: Some(vec![".".to_string()]), + work_done_progress_options: Default::default(), + all_commit_characters: None, + ..Default::default() + }), + execute_command_provider: Some(ExecuteCommandOptions { + commands: vec!["dummy.do_something".to_string()], + work_done_progress_options: Default::default(), + }), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + file_operations: None, + }), + ..ServerCapabilities::default() + }, + }) + } + + async fn initialized(&self, _: InitializedParams) { + self.client + .log_message(MessageType::INFO, "initialized!") + .await; + } + + async fn shutdown(&self) -> Result<()> { + Ok(()) + } + + async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) { + self.client + .log_message(MessageType::INFO, "workspace folders changed!") + .await; + } + + async fn did_change_configuration(&self, _: DidChangeConfigurationParams) { + self.client + .log_message(MessageType::INFO, "configuration changed!") + .await; + } + + async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) { + self.client + .log_message(MessageType::INFO, "watched files have changed!") + .await; + } + + async fn execute_command(&self, _: ExecuteCommandParams) -> Result> { + self.client + .log_message(MessageType::INFO, "command executed!") + .await; + + match self.client.apply_edit(WorkspaceEdit::default()).await { + Ok(res) if res.applied => self.client.log_message(MessageType::INFO, "applied").await, + Ok(_) => self.client.log_message(MessageType::INFO, "rejected").await, + Err(err) => self.client.log_message(MessageType::ERROR, err).await, + } + + Ok(None) + } + + async fn did_open(&self, _: DidOpenTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file opened!") + .await; + } + + async fn did_change(&self, _: DidChangeTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file changed!") + .await; + } + + async fn did_save(&self, p: DidSaveTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file saved!") + .await; + let diags = read_norminette(&Path::new(p.text_document.uri.as_str())) + .expect("norminette read failed"); + self.client + .publish_diagnostics(p.text_document.uri, diags, None) + .await + } + + async fn did_close(&self, _: DidCloseTextDocumentParams) { + self.client + .log_message(MessageType::INFO, "file closed!") + .await; + } + + async fn completion(&self, _: CompletionParams) -> Result> { + Ok(Some(CompletionResponse::Array(vec![ + CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()), + CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()), + ]))) + } +} + +pub fn read_norminette(path: &Path) -> io::Result> { + let output = std::process::Command::new("norminette") + .arg(path) + .output()?; + let (_, diags) = parse_norminette(&String::from_utf8(output.stdout).expect("not valid utf8")) + .unwrap_or_else(|err| panic!("norminette parse error: {}", err)); + + Ok(diags.into_iter().map(|d| d.to_diagnostic()).collect()) +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt().init(); + + let (stdin, stdout) = (tokio::io::stdin(), tokio::io::stdout()); + + let (service, socket) = LspService::new(|client| Backend { client }); + Server::new(stdin, stdout, socket).serve(service).await; +} diff --git a/src/norminette_msg.rs b/src/norminette_msg.rs new file mode 100644 index 0000000..aab3eeb --- /dev/null +++ b/src/norminette_msg.rs @@ -0,0 +1,71 @@ +use std::u32; + +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; + +#[derive(Debug)] +pub enum NorminetteMsg { + NoLocation { + message: String, + }, + Line { + line: i32, + message: String, + }, + LineColumn { + error_type: String, + line: i32, + column: i32, + message: String, + }, +} + +impl NorminetteMsg { + pub fn find_range(&self) -> Range { + match self { + NorminetteMsg::NoLocation { message: _ } => Range { + start: Position::new(0, 0), + end: Position::new(1, u32::MAX), + }, + NorminetteMsg::Line { line, message: _ } => Range { + start: Position::new((line - 1) as u32, 0), + end: Position::new(*line as u32, u32::MAX), + }, + NorminetteMsg::LineColumn { + error_type: _, + line, + column, + message: _, + } => Range { + start: Position::new((line - 1) as u32, *column as u32), + end: Position::new(*line as u32, u32::MAX), + }, + } + } + + pub fn to_diagnostic(&self) -> Diagnostic { + Diagnostic { + range: self.find_range(), + severity: Some(DiagnosticSeverity::ERROR), + code: None, + code_description: None, + source: Some("norminette".to_string()), + message: self.message().to_string(), + related_information: None, + tags: None, + data: None, + } + } + + fn message(&self) -> &str { + match self { + NorminetteMsg::NoLocation { message } => message, + NorminetteMsg::Line { line: _, message } => message, + NorminetteMsg::LineColumn { + error_type: _, + line: _, + column: _, + message, + } => message, + } + } +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..780b6ca --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,61 @@ +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{anychar, i32, newline, none_of, space0}, + combinator::eof, + multi::{many0, many1, many_m_n, many_till}, + IResult, Parser, +}; + +use crate::norminette_msg::NorminetteMsg; + +pub fn parse_norminette(s: &str) -> IResult<&str, Vec> { + let (s, _) = many_m_n(0, 1, header)(s)?; + let (s, _) = many0(none_of("\n"))(s)?; // line of the form "filename: Error!" or "filename: OK!" + let (s, _) = newline(s)?; + many0(alt((location, invalid)))(s) +} + +fn header(s: &str) -> IResult<&str, ()> { + let (s, _) = tag("Missing or invalid header")(s)?; + let (s, _) = many0(none_of("\n"))(s)?; + let (s, _) = newline(s)?; + Ok((s, ())) +} + +fn invalid(s: &str) -> IResult<&str, NorminetteMsg> { + let (s, _) = many1(none_of(" "))(s)?; + let (s, _) = many0(anychar)(s)?; + Ok(( + s, + NorminetteMsg::NoLocation { + message: format!("file{}", s), + }, + )) +} + +fn location(s: &str) -> IResult<&str, NorminetteMsg> { + let (s, _) = tag("Error:")(s)?; + let (s, _) = space0(s)?; + let (s, error_type) = many0(none_of("\n"))(s)?; + let (s, _) = space0(s)?; + let (s, _) = tag("(line: ")(s)?; + let (s, _) = space0(s)?; + let (s, l) = i32(s)?; + let (s, _) = tag(", col:")(s)?; + let (s, _) = space0(s)?; + let (s, c) = i32(s)?; + let (s, _) = tag("):\t")(s)?; + let (s, _) = space0(s)?; + let (s, (msg, _)) = many_till(anychar, alt((newline.map(|_| ""), eof)))(s)?; + + Ok(( + s, + NorminetteMsg::LineColumn { + error_type: error_type.into_iter().collect(), + line: l, + column: c, + message: msg.into_iter().collect(), + }, + )) +}