Skip to content

Commit

Permalink
basic sketch 2
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidLee18 committed Sep 15, 2024
1 parent 09fa658 commit 16cc70c
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Cargo.toml
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"
150 changes: 150 additions & 0 deletions src/main.rs
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;
}
71 changes: 71 additions & 0 deletions src/norminette_msg.rs
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,
}
}
}
61 changes: 61 additions & 0 deletions src/parser.rs
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(),
},
))
}

0 comments on commit 16cc70c

Please sign in to comment.