diff --git a/Cargo.lock b/Cargo.lock index aa750753d0..407bbec7c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2639,9 +2639,11 @@ dependencies = [ "parking_lot", "roc_can", "roc_collections", + "roc_fmt", "roc_load", "roc_module", "roc_packaging", + "roc_parse", "roc_problem", "roc_region", "roc_reporting", diff --git a/crates/compiler/region/src/all.rs b/crates/compiler/region/src/all.rs index c890b20a0a..edcc6f8987 100644 --- a/crates/compiler/region/src/all.rs +++ b/crates/compiler/region/src/all.rs @@ -406,6 +406,10 @@ impl LineInfo { let end = self.convert_line_column(lc_region.end); Region::new(start, end) } + + pub fn num_lines(&self) -> u32 { + self.line_offsets.len() as u32 + } } #[test] diff --git a/crates/lang_srv/Cargo.toml b/crates/lang_srv/Cargo.toml index 8bdc88d86e..c8cd6b7c1f 100644 --- a/crates/lang_srv/Cargo.toml +++ b/crates/lang_srv/Cargo.toml @@ -10,8 +10,10 @@ path = "src/server.rs" [dependencies] roc_can = { path = "../compiler/can" } roc_collections = { path = "../compiler/collections" } +roc_fmt = { path = "../compiler/fmt" } roc_load = { path = "../compiler/load" } roc_module = { path = "../compiler/module" } +roc_parse = { path = "../compiler/parse" } roc_problem = { path = "../compiler/problem" } roc_region = { path = "../compiler/region" } roc_reporting = { path = "../reporting" } diff --git a/crates/lang_srv/src/analysis.rs b/crates/lang_srv/src/analysis.rs index d891540a57..67dcd85d5b 100644 --- a/crates/lang_srv/src/analysis.rs +++ b/crates/lang_srv/src/analysis.rs @@ -3,16 +3,18 @@ use std::path::{Path, PathBuf}; use bumpalo::Bump; use roc_can::{abilities::AbilitiesStore, expr::Declarations}; use roc_collections::MutMap; +use roc_fmt::{Ast, Buf}; use roc_load::{CheckedModule, LoadedModule}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_packaging::cache::{self, RocCacheDir}; -use roc_region::all::{LineInfo, Region}; +use roc_parse::parser::SyntaxError; +use roc_region::all::LineInfo; use roc_reporting::report::RocDocAllocator; use roc_solve_problem::TypeError; use roc_types::subs::Subs; use tower_lsp::lsp_types::{ Diagnostic, GotoDefinitionResponse, Hover, HoverContents, Location, MarkedString, Position, - Range, Url, + Range, TextEdit, Url, }; use crate::convert::{ @@ -55,6 +57,7 @@ impl GlobalAnalysis { let analyzed_document = AnalyzedDocument { url: source_url, line_info, + source, module: None, diagnostics: all_problems, }; @@ -178,6 +181,7 @@ impl<'a> AnalyzedDocumentBuilder<'a> { AnalyzedDocument { url: Url::from_file_path(path).unwrap(), line_info, + source: source.into(), module: Some(analyzed_module), diagnostics, } @@ -234,6 +238,7 @@ struct AnalyzedModule { pub(crate) struct AnalyzedDocument { url: Url, line_info: LineInfo, + source: String, module: Option, diagnostics: Vec, } @@ -266,6 +271,13 @@ impl AnalyzedDocument { } } + fn whole_document_range(&self) -> Range { + let line_info = self.line_info(); + let start = Position::new(0, 0); + let end = Position::new(line_info.num_lines(), 0); + Range::new(start, end) + } + pub fn diagnostics(&mut self) -> Vec { self.diagnostics.clone() } @@ -320,7 +332,7 @@ impl AnalyzedDocument { }) } - pub fn goto_definition(&self, symbol: Symbol) -> Option { + pub fn definition(&self, symbol: Symbol) -> Option { let AnalyzedModule { declarations, .. } = self.module()?; let found_declaration = roc_can::traverse::find_declaration(symbol, declarations)?; @@ -329,4 +341,44 @@ impl AnalyzedDocument { Some(GotoDefinitionResponse::Scalar(self.location(range))) } + + pub fn format(&self) -> Option> { + let source = &self.source; + let arena = &Bump::new(); + let ast = parse_all(arena, &self.source).ok()?; + let mut buf = Buf::new_in(arena); + fmt_all(&mut buf, &ast); + let new_source = buf.as_str(); + + if source == new_source { + None + } else { + let range = self.whole_document_range(); + let text_edit = TextEdit::new(range, new_source.to_string()); + Some(vec![text_edit]) + } + } +} + +fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> { + use roc_parse::{ + module::{module_defs, parse_header}, + parser::Parser, + state::State, + }; + + let (module, state) = parse_header(arena, State::new(src.as_bytes())) + .map_err(|e| SyntaxError::Header(e.problem))?; + + let (_, defs, _) = module_defs().parse(arena, state, 0).map_err(|(_, e)| e)?; + + Ok(Ast { module, defs }) +} + +fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a Ast) { + roc_fmt::module::fmt_module(buf, &ast.module); + + roc_fmt::def::fmt_defs(buf, &ast.defs, 0); + + buf.fmt_end_of_file(); } diff --git a/crates/lang_srv/src/registry.rs b/crates/lang_srv/src/registry.rs index f6ab506244..c01677ae19 100644 --- a/crates/lang_srv/src/registry.rs +++ b/crates/lang_srv/src/registry.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use roc_module::symbol::ModuleId; -use tower_lsp::lsp_types::{Diagnostic, GotoDefinitionResponse, Hover, Position, Url}; +use tower_lsp::lsp_types::{Diagnostic, GotoDefinitionResponse, Hover, Position, TextEdit, Url}; use crate::analysis::{AnalyzedDocument, GlobalAnalysis}; @@ -67,6 +67,11 @@ impl Registry { ) -> Option { let symbol = self.document_by_url(url)?.symbol_at(position)?; let def_document = self.document_by_module_id(symbol.module_id())?; - def_document.goto_definition(symbol) + def_document.definition(symbol) + } + + pub fn formatting(&mut self, url: &Url) -> Option> { + let document = self.document_by_url(url)?; + document.format() } } diff --git a/crates/lang_srv/src/server.rs b/crates/lang_srv/src/server.rs index 4ddb3de6c6..0babc21812 100644 --- a/crates/lang_srv/src/server.rs +++ b/crates/lang_srv/src/server.rs @@ -43,11 +43,17 @@ impl RocLs { work_done_progress: None, }, }; + let document_formatting_provider = DocumentFormattingOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + }; ServerCapabilities { text_document_sync: Some(text_document_sync), hover_provider: Some(hover_provider), definition_provider: Some(OneOf::Right(definition_provider)), + document_formatting_provider: Some(OneOf::Right(document_formatting_provider)), ..ServerCapabilities::default() } } @@ -146,6 +152,16 @@ impl LanguageServer for RocLs { .goto_definition(&text_document.uri, position) }) } + + async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { + let DocumentFormattingParams { + text_document, + options: _, + work_done_progress_params: _, + } = params; + + panic_wrapper(|| self.registry().formatting(&text_document.uri)) + } } fn panic_wrapper(f: impl FnOnce() -> Option + std::panic::UnwindSafe) -> Result> {