Skip to content

Commit

Permalink
Merge pull request #236 from tdejager/feature/lsp/filesystem
Browse files Browse the repository at this point in the history
feature: initial LSP support
  • Loading branch information
baszalmstra authored Jul 17, 2020
2 parents 59a091b + 5a98295 commit 98979ca
Show file tree
Hide file tree
Showing 29 changed files with 1,154 additions and 82 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,39 @@ git submodule update --init --recursive
cargo build --release
```

## Language server
Mun contains initial support for the lsp protocol, start the executable using:

```bash
mun language-server
```

Currently, only diagnostics are supported.

### VS code

To run in [Visual Studio Code](https://code.visualstudio.com/). Use the following extension:
[VS code extension](https://github.com/mun-lang/vscode-extension).

### Vim/Neovim
Use a language server plugin (or built-in lsp support of neovim), for example using [coc.nvim](https://github.com/neoclide/coc.nvim).

Paste the following config into your `:CocConfig`, replace the `command`, with the correct path to the mun executable.

```json
"languageserver": {
"mun": {
"command": "<path_to_mun>",
"rootPatterns": ["mun.toml"],
"trace.server": "verbose",
"args": ["language-server"],
"filetypes": ["mun"]
}
}
```

Note that, `"trace.server": "verbose"` is optional and helps with language server debugging.

## Building Documentation

Building the book requires
Expand Down
2 changes: 1 addition & 1 deletion crates/mun_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mun_target = { version = "=0.2.0", path = "../mun_target" }
mun_lld = { version = "=70.2.0", path = "../mun_lld" }
anyhow = "1.0.31"
thiserror = "1.0.19"
salsa="0.12"
salsa="0.14"
md5="0.6.1"
array-init="0.1.0"
tempfile = "3"
Expand Down
4 changes: 4 additions & 0 deletions crates/mun_codegen/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl salsa::Database for MockDatabase {
&self.runtime
}

fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime<MockDatabase> {
&mut self.runtime
}

fn salsa_event(&self, event: impl Fn() -> salsa::Event<MockDatabase>) {
let mut events = self.events.lock();
if let Some(events) = &mut *events {
Expand Down
4 changes: 4 additions & 0 deletions crates/mun_compiler/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ impl salsa::Database for CompilerDatabase {
fn salsa_runtime(&self) -> &salsa::Runtime<CompilerDatabase> {
&self.runtime
}

fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime<CompilerDatabase> {
&mut self.runtime
}
}
2 changes: 1 addition & 1 deletion crates/mun_compiler/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec<Snippet> {
// correctly replace each `\t` into 1-4 space.
let source_code = db.file_text(file_id).to_string().replace("\t", " ");

let relative_file_path = db.file_relative_path(file_id).display().to_string();
let relative_file_path = db.file_relative_path(file_id).to_string();

let line_index = db.line_index(file_id);

Expand Down
6 changes: 3 additions & 3 deletions crates/mun_compiler/src/driver/display_color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ fn cmd_supports_ansi() -> bool {
let windows_version = windows_version
.split(' ') // split to drop "Microsoft", "Windows" and "[Version" from string
.last() // latest element contains Windows version with noisy ']' char
.and_then(|window_version| {
.map(|window_version| {
let mut window_version: String = window_version.trim().to_string();

// Remove ']' char
window_version.pop();

let window_version: Vec<&str> = window_version.split('.').collect();

Some((
(
window_version[0].parse::<usize>(),
window_version[1].parse::<usize>(),
window_version[2].parse::<usize>(),
))
)
});

if let Some((Ok(major), Ok(minor), Ok(patch))) = windows_version {
Expand Down
12 changes: 4 additions & 8 deletions crates/mun_compiler_daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn compile_and_watch_manifest(
Write(ref path) if is_source_file(path) => {
let relative_path = compute_source_relative_path(&source_directory, path)?;
let file_contents = std::fs::read_to_string(path)?;
log::info!("Modifying {}", relative_path.display());
log::info!("Modifying {}", relative_path);
driver.update_file(relative_path, file_contents);
if !driver.emit_diagnostics(&mut stderr())? {
driver.write_all_assemblies()?;
Expand All @@ -55,7 +55,7 @@ pub fn compile_and_watch_manifest(
Create(ref path) if is_source_file(path) => {
let relative_path = compute_source_relative_path(&source_directory, path)?;
let file_contents = std::fs::read_to_string(path)?;
log::info!("Creating {}", relative_path.display());
log::info!("Creating {}", relative_path);
driver.add_file(relative_path, file_contents);
if !driver.emit_diagnostics(&mut stderr())? {
driver.write_all_assemblies()?;
Expand All @@ -64,7 +64,7 @@ pub fn compile_and_watch_manifest(
Remove(ref path) if is_source_file(path) => {
// Simply remove the source file from the source root
let relative_path = compute_source_relative_path(&source_directory, path)?;
log::info!("Removing {}", relative_path.display());
log::info!("Removing {}", relative_path);
let assembly_path = driver.assembly_output_path(driver.get_file_id_for_path(&relative_path).expect("cannot remove a file that was not part of the compilation in the first place"));
if assembly_path.is_file() {
std::fs::remove_file(assembly_path)?;
Expand All @@ -79,11 +79,7 @@ pub fn compile_and_watch_manifest(
let from_relative_path = compute_source_relative_path(&source_directory, from)?;
let to_relative_path = compute_source_relative_path(&source_directory, to)?;

log::info!(
"Renaming {} to {}",
from_relative_path.display(),
to_relative_path.display(),
);
log::info!("Renaming {} to {}", from_relative_path, to_relative_path,);
driver.rename(from_relative_path, to_relative_path);
if !driver.emit_diagnostics(&mut stderr())? {
driver.write_all_assemblies()?;
Expand Down
4 changes: 2 additions & 2 deletions crates/mun_hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ keywords = ["game", "hot-reloading", "language", "mun", "scripting"]
categories = ["game-development", "mun"]

[dependencies]
salsa="0.12"
salsa="0.14"
superslice = "1.0"
mun_syntax = { version = "=0.2.0", path = "../mun_syntax" }
mun_target = { version = "=0.2.0", path = "../mun_target" }
rustc-hash = "1.1"
once_cell = "0.2"
relative-path = "0.4.0"
relative-path = "1.2"
ena = "0.14"
drop_bomb = "0.1.4"
either = "1.5.3"
Expand Down
7 changes: 1 addition & 6 deletions crates/mun_hir/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ use crate::{
ids,
line_index::LineIndex,
name_resolution::ModuleScope,
source_id::ErasedFileAstId,
ty::InferenceResult,
AstIdMap, ExprScopes, FileId, RawItems, Struct,
};
use mun_syntax::{ast, Parse, SourceFile, SyntaxNode};
use mun_syntax::{ast, Parse, SourceFile};
use mun_target::abi;
use mun_target::spec::Target;
pub use relative_path::RelativePathBuf;
Expand Down Expand Up @@ -55,10 +54,6 @@ pub trait DefDatabase: SourceDatabase {
#[salsa::invoke(crate::source_id::AstIdMap::ast_id_map_query)]
fn ast_id_map(&self, file_id: FileId) -> Arc<AstIdMap>;

/// Returns the corresponding AST node of a type erased ast id
#[salsa::invoke(crate::source_id::AstIdMap::file_item_query)]
fn ast_id_to_node(&self, file_id: FileId, ast_id: ErasedFileAstId) -> SyntaxNode;

/// Returns the raw items of a file
#[salsa::invoke(RawItems::raw_file_items_query)]
fn raw_items(&self, file_id: FileId) -> Arc<RawItems>;
Expand Down
4 changes: 2 additions & 2 deletions crates/mun_hir/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ pub(crate) trait AstItemDef<N: AstNode>: salsa::InternKey + Clone {
fn source(self, db: &impl DefDatabase) -> InFile<N> {
let loc = self.lookup_intern(db);
let ast = loc.ast_id.to_node(db);
InFile::new(loc.ast_id.file_id(), ast)
InFile::new(loc.ast_id.file_id, ast)
}

fn file_id(self, db: &impl DefDatabase) -> FileId {
self.lookup_intern(db).ast_id.file_id()
self.lookup_intern(db).ast_id.file_id
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/mun_hir/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ impl salsa::Database for MockDatabase {
&self.runtime
}

fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime<MockDatabase> {
&mut self.runtime
}

fn salsa_event(&self, event: impl Fn() -> salsa::Event<MockDatabase>) {
let mut events = self.events.lock();
if let Some(events) = &mut *events {
Expand Down
52 changes: 15 additions & 37 deletions crates/mun_hir/src/source_id.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
use crate::in_file::InFile;
use crate::{db::DefDatabase, Arena, FileId, RawId};
use mun_syntax::{ast, AstNode, SyntaxNode, SyntaxNodePtr};
use mun_syntax::{ast, AstNode, AstPtr, SyntaxNode, SyntaxNodePtr};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::sync::Arc;

/// `AstId` points to an AST node in any file.
///
/// It is stable across reparses, and can be used as salsa key/value.
#[derive(Debug)]
pub(crate) struct AstId<N: AstNode> {
file_id: FileId,
file_ast_id: FileAstId<N>,
}

impl<N: AstNode> Clone for AstId<N> {
fn clone(&self) -> AstId<N> {
*self
}
}
impl<N: AstNode> Copy for AstId<N> {}

impl<N: AstNode> PartialEq for AstId<N> {
fn eq(&self, other: &Self) -> bool {
(self.file_id, self.file_ast_id) == (other.file_id, other.file_ast_id)
}
}
impl<N: AstNode> Eq for AstId<N> {}
impl<N: AstNode> Hash for AstId<N> {
fn hash<H: Hasher>(&self, hasher: &mut H) {
(self.file_id, self.file_ast_id).hash(hasher);
}
}
pub(crate) type AstId<N> = InFile<FileAstId<N>>;

impl<N: AstNode> AstId<N> {
pub(crate) fn file_id(self) -> FileId {
self.file_id
}

pub(crate) fn to_node(self, db: &impl DefDatabase) -> N {
let syntax_node = db.ast_id_to_node(self.file_id, self.file_ast_id.raw);
N::cast(syntax_node).unwrap()
pub fn to_node(&self, db: &dyn DefDatabase) -> N {
let root = db.parse(self.file_id);
db.ast_id_map(self.file_id)
.get(self.value)
.to_node(&root.syntax_node())
}
}

#[derive(Debug)]
pub(crate) struct FileAstId<N: AstNode> {
raw: ErasedFileAstId,
_ty: PhantomData<N>,
_ty: PhantomData<fn() -> N>,
}

impl<N: AstNode> Clone for FileAstId<N> {
Expand All @@ -70,10 +46,7 @@ impl<N: AstNode> Hash for FileAstId<N> {

impl<N: AstNode> FileAstId<N> {
pub(crate) fn with_file_id(self, file_id: FileId) -> AstId<N> {
AstId {
file_id,
file_ast_id: self,
}
AstId::new(file_id, self)
}
}

Expand Down Expand Up @@ -138,6 +111,11 @@ impl AstIdMap {
res
}

/// Returns the `AstPtr` of the given id.
pub(crate) fn get<N: AstNode>(&self, id: FileAstId<N>) -> AstPtr<N> {
self.arena[id.raw].try_cast::<N>().unwrap()
}

/// Constructs a new `ErasedFileAstId` from a `SyntaxNode`
fn alloc(&mut self, item: &SyntaxNode) -> ErasedFileAstId {
self.arena.alloc(SyntaxNodePtr::new(item))
Expand Down
7 changes: 7 additions & 0 deletions crates/mun_language_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ async-std = "1.6"
futures = "0.3"
anyhow = "1.0"
thiserror = "1.0"
ra_vfs = "0.6.1"
salsa = "0.14"
hir = { version = "=0.2.0", path="../mun_hir", package="mun_hir" }
rayon = "1.3"
num_cpus = "1.13.0"
mun_target = { version = "=0.2.0", path = "../mun_target" }
mun_syntax = { version = "=0.2.0", path = "../mun_syntax" }
81 changes: 81 additions & 0 deletions crates/mun_language_server/src/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::cancelation::Canceled;
use crate::change::AnalysisChange;
use crate::db::AnalysisDatabase;
use crate::diagnostics;
use crate::diagnostics::Diagnostic;
use hir::line_index::LineIndex;
use hir::SourceDatabase;
use salsa::{ParallelDatabase, Snapshot};
use std::sync::Arc;

/// Result of an operation that can be canceled.
pub type Cancelable<T> = Result<T, Canceled>;

/// The `Analysis` struct is the basis of all language server operations. It maintains the current
/// state of the source.
pub struct Analysis {
db: AnalysisDatabase,
}

impl Analysis {
pub fn new() -> Self {
Analysis {
db: AnalysisDatabase::new(),
}
}

/// Applies the given changes to the state. If there are outstanding `AnalysisSnapshot`s they
/// will be canceled.
pub fn apply_change(&mut self, change: AnalysisChange) {
self.db.apply_change(change)
}

/// Creates a snapshot of the current `Analysis`. You can query the resulting `AnalysisSnapshot`
/// to get analysis and diagnostics.
pub fn snapshot(&self) -> AnalysisSnapshot {
AnalysisSnapshot {
db: self.db.snapshot(),
}
}
}

/// The `AnalysisSnapshot` is a snapshot of the state of the source, it enables querying for
/// the snapshot in a consistent state.
///
/// A `AnalysisSnapshot` is created by calling `Analysis::snapshot`. When applying changes to the
/// `Analysis` struct through the use of `Analysis::apply_changes` all snapshots are cancelled (most
/// methods return `Err(Canceled)`).
pub struct AnalysisSnapshot {
db: Snapshot<AnalysisDatabase>,
}

impl AnalysisSnapshot {
/// Computes the set of diagnostics for the given file.
pub fn diagnostics(&self, file_id: hir::FileId) -> Cancelable<Vec<Diagnostic>> {
self.with_db(|db| diagnostics::diagnostics(db, file_id))
}

/// Returns all the files in the given source root
pub fn source_root_files(
&self,
source_root: hir::SourceRootId,
) -> Cancelable<Vec<hir::FileId>> {
self.with_db(|db| {
let source_root = db.source_root(source_root);
source_root.files().collect()
})
}

/// Returns the line index for the specified file
pub fn file_line_index(&self, file_id: hir::FileId) -> Cancelable<Arc<LineIndex>> {
self.with_db(|db| db.line_index(file_id))
}

/// Performs an operation on that may be Canceled.
fn with_db<F: FnOnce(&AnalysisDatabase) -> T + std::panic::UnwindSafe, T>(
&self,
f: F,
) -> Cancelable<T> {
self.db.catch_canceled(f)
}
}
Loading

0 comments on commit 98979ca

Please sign in to comment.