-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
09fa658
commit 16cc70c
Showing
4 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Mutex<?>> | ||
} | ||
|
||
#[tower_lsp::async_trait] | ||
impl LanguageServer for Backend { | ||
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> { | ||
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<Option<Value>> { | ||
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<Option<CompletionResponse>> { | ||
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<Vec<Diagnostic>> { | ||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<NorminetteMsg>> { | ||
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(), | ||
}, | ||
)) | ||
} |