mirror of
https://github.com/oxalica/nil.git
synced 2024-11-25 18:41:40 +03:00
Allow disable diagnostics for some files
This commit is contained in:
parent
9794a2eb97
commit
84e39a65c9
16
README.md
16
README.md
@ -78,7 +78,13 @@ Merge this setting into your `coc-settings.json`, which can be opened by `:CocCo
|
|||||||
"nix": {
|
"nix": {
|
||||||
"command": "nil",
|
"command": "nil",
|
||||||
"filetypes": ["nix"],
|
"filetypes": ["nix"],
|
||||||
"rootPatterns": ["flake.nix"]
|
"rootPatterns": ["flake.nix"],
|
||||||
|
// Uncomment these to tweak settings.
|
||||||
|
// "settings": {
|
||||||
|
// "nil": {
|
||||||
|
// "formatting": { "command": ["nixpkgs-fmt"] }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,9 +133,15 @@ Modify the extension's settings in your `settings.json`.
|
|||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
// ...
|
|
||||||
"nix.enableLanguageServer": true, // Enable LSP.
|
"nix.enableLanguageServer": true, // Enable LSP.
|
||||||
"nix.serverPath": "nil" // The path to the LSP server executable.
|
"nix.serverPath": "nil" // The path to the LSP server executable.
|
||||||
|
|
||||||
|
// Uncomment these to tweak settings.
|
||||||
|
// "nix.serverSettings": {
|
||||||
|
// "nil": {
|
||||||
|
// "formatting": { "command": ["nixpkgs-fmt"] }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
75
crates/nil/src/config.rs
Normal file
75
crates/nil/src/config.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use lsp_types::Url;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const CONFIG_KEY: &str = "nil";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub root_path: PathBuf,
|
||||||
|
|
||||||
|
pub diagnostics_excluded_files: Vec<Url>,
|
||||||
|
pub diagnostics_ignored: HashSet<String>,
|
||||||
|
pub formatting_command: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn new(root_path: PathBuf) -> Self {
|
||||||
|
assert!(root_path.is_absolute());
|
||||||
|
Self {
|
||||||
|
root_path,
|
||||||
|
diagnostics_excluded_files: Vec::new(),
|
||||||
|
diagnostics_ignored: HashSet::new(),
|
||||||
|
formatting_command: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, mut value: serde_json::Value) -> (Vec<String>, bool) {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
let mut updated_diagnostics = false;
|
||||||
|
|
||||||
|
if let Some(v) = value.pointer_mut("/diagnostics/excludedFiles") {
|
||||||
|
match serde_json::from_value::<Vec<String>>(v.take()) {
|
||||||
|
Ok(v) => {
|
||||||
|
self.diagnostics_excluded_files = v
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| {
|
||||||
|
Url::from_file_path(self.root_path.join(path))
|
||||||
|
.expect("Root path is absolute")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
updated_diagnostics = true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errors.push(format!("Invalid value of `diagnostics.excludedFiles`: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(v) = value.pointer_mut("/diagnostics/ignored") {
|
||||||
|
match serde_json::from_value(v.take()) {
|
||||||
|
Ok(v) => {
|
||||||
|
self.diagnostics_ignored = v;
|
||||||
|
updated_diagnostics = true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errors.push(format!("Invalid value of `diagnostics.ignored`: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(v) = value.pointer_mut("/formatting/command") {
|
||||||
|
match serde_json::from_value::<Option<Vec<String>>>(v.take()) {
|
||||||
|
Ok(Some(v)) if v.is_empty() => {
|
||||||
|
errors.push("`formatting.command` must not be an empty list".into());
|
||||||
|
}
|
||||||
|
Ok(v) => {
|
||||||
|
self.formatting_command = v;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
errors.push(format!("Invalid value of `formatting.command`: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(errors, updated_diagnostics)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
mod capabilities;
|
mod capabilities;
|
||||||
|
mod config;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod handler;
|
mod handler;
|
||||||
mod semantic_tokens;
|
mod semantic_tokens;
|
||||||
@ -36,7 +37,16 @@ pub fn main_loop(conn: Connection) -> Result<()> {
|
|||||||
|
|
||||||
let init_params = serde_json::from_value::<InitializeParams>(init_params)?;
|
let init_params = serde_json::from_value::<InitializeParams>(init_params)?;
|
||||||
|
|
||||||
let mut server = Server::new(conn.sender.clone());
|
let root_path = match init_params
|
||||||
|
.root_uri
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|uri| uri.to_file_path().ok())
|
||||||
|
{
|
||||||
|
Some(path) => path,
|
||||||
|
None => std::env::current_dir()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut server = Server::new(conn.sender.clone(), root_path);
|
||||||
server.run(conn.receiver, init_params)?;
|
server.run(conn.receiver, init_params)?;
|
||||||
|
|
||||||
tracing::info!("Leaving main loop");
|
tracing::info!("Leaving main loop");
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::config::{Config, CONFIG_KEY};
|
||||||
use crate::{convert, handler, Result, Vfs};
|
use crate::{convert, handler, Result, Vfs};
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use ide::{Analysis, AnalysisHost, Cancelled};
|
use ide::{Analysis, AnalysisHost, Cancelled};
|
||||||
@ -8,28 +9,24 @@ use lsp_types::{
|
|||||||
InitializeParams, MessageType, NumberOrString, PublishDiagnosticsParams, ShowMessageParams,
|
InitializeParams, MessageType, NumberOrString, PublishDiagnosticsParams, ShowMessageParams,
|
||||||
Url,
|
Url,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashMap;
|
||||||
use std::panic::UnwindSafe;
|
use std::panic::UnwindSafe;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Once, RwLock};
|
use std::sync::{Arc, Once, RwLock};
|
||||||
use std::{panic, thread};
|
use std::{panic, thread};
|
||||||
|
|
||||||
const CONFIG_KEY: &str = "nil";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
pub(crate) diagnostics_ignored: HashSet<String>,
|
|
||||||
pub(crate) formatting_command: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReqHandler = fn(&mut Server, Response);
|
type ReqHandler = fn(&mut Server, Response);
|
||||||
|
|
||||||
type Task = Box<dyn FnOnce() -> Event + Send>;
|
type Task = Box<dyn FnOnce() -> Event + Send>;
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
Response(Response),
|
Response(Response),
|
||||||
Diagnostics(Url, Vec<Diagnostic>),
|
Diagnostics {
|
||||||
|
uri: Url,
|
||||||
|
version: u64,
|
||||||
|
diagnostics: Vec<Diagnostic>,
|
||||||
|
},
|
||||||
ClientExited,
|
ClientExited,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +35,11 @@ pub struct Server {
|
|||||||
/// This contains an internal RWLock and must not lock together with `vfs`.
|
/// This contains an internal RWLock and must not lock together with `vfs`.
|
||||||
host: AnalysisHost,
|
host: AnalysisHost,
|
||||||
vfs: Arc<RwLock<Vfs>>,
|
vfs: Arc<RwLock<Vfs>>,
|
||||||
opened_files: HashSet<Url>,
|
opened_files: HashMap<Url, FileData>,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
is_shutdown: bool,
|
is_shutdown: bool,
|
||||||
|
/// Monotonic version counter for diagnostics calculation ordering.
|
||||||
|
version_counter: u64,
|
||||||
|
|
||||||
// Message passing.
|
// Message passing.
|
||||||
req_queue: ReqQueue<(), ReqHandler>,
|
req_queue: ReqQueue<(), ReqHandler>,
|
||||||
@ -50,8 +49,14 @@ pub struct Server {
|
|||||||
event_rx: Receiver<Event>,
|
event_rx: Receiver<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FileData {
|
||||||
|
diagnostics_version: u64,
|
||||||
|
diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(lsp_tx: Sender<Message>) -> Self {
|
pub fn new(lsp_tx: Sender<Message>, root_path: PathBuf) -> Self {
|
||||||
let (task_tx, task_rx) = crossbeam_channel::unbounded();
|
let (task_tx, task_rx) = crossbeam_channel::unbounded();
|
||||||
let (event_tx, event_rx) = crossbeam_channel::unbounded();
|
let (event_tx, event_rx) = crossbeam_channel::unbounded();
|
||||||
let worker_cnt = thread::available_parallelism().map_or(1, |n| n.get());
|
let worker_cnt = thread::available_parallelism().map_or(1, |n| n.get());
|
||||||
@ -69,8 +74,9 @@ impl Server {
|
|||||||
host: Default::default(),
|
host: Default::default(),
|
||||||
vfs: Arc::new(RwLock::new(Vfs::new())),
|
vfs: Arc::new(RwLock::new(Vfs::new())),
|
||||||
opened_files: Default::default(),
|
opened_files: Default::default(),
|
||||||
config: Arc::new(Config::default()),
|
config: Arc::new(Config::new(root_path)),
|
||||||
is_shutdown: false,
|
is_shutdown: false,
|
||||||
|
version_counter: 0,
|
||||||
|
|
||||||
req_queue: ReqQueue::default(),
|
req_queue: ReqQueue::default(),
|
||||||
lsp_tx,
|
lsp_tx,
|
||||||
@ -164,13 +170,26 @@ impl Server {
|
|||||||
self.lsp_tx.send(resp.into()).unwrap();
|
self.lsp_tx.send(resp.into()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Diagnostics(uri, diagnostics) => {
|
Event::Diagnostics {
|
||||||
self.send_notification::<notif::PublishDiagnostics>(PublishDiagnosticsParams {
|
uri,
|
||||||
uri,
|
version,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
version: None,
|
} => match self.opened_files.get_mut(&uri) {
|
||||||
});
|
Some(f) if f.diagnostics_version < version => {
|
||||||
}
|
f.diagnostics_version = version;
|
||||||
|
f.diagnostics = diagnostics.clone();
|
||||||
|
tracing::trace!(
|
||||||
|
"Push {} diagnostics of {uri}, version {version}",
|
||||||
|
diagnostics.len(),
|
||||||
|
);
|
||||||
|
self.send_notification::<notif::PublishDiagnostics>(PublishDiagnosticsParams {
|
||||||
|
uri,
|
||||||
|
diagnostics,
|
||||||
|
version: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => tracing::debug!("Ignore raced diagnostics of {uri}, version {version}"),
|
||||||
|
},
|
||||||
Event::ClientExited => {
|
Event::ClientExited => {
|
||||||
return Err("The process initializing this server is exited. Stopping.".into());
|
return Err("The process initializing this server is exited. Stopping.".into());
|
||||||
}
|
}
|
||||||
@ -224,7 +243,7 @@ impl Server {
|
|||||||
})?
|
})?
|
||||||
.on_sync_mut::<notif::DidOpenTextDocument>(|st, params| {
|
.on_sync_mut::<notif::DidOpenTextDocument>(|st, params| {
|
||||||
let uri = ¶ms.text_document.uri;
|
let uri = ¶ms.text_document.uri;
|
||||||
st.opened_files.insert(uri.clone());
|
st.opened_files.insert(uri.clone(), FileData::default());
|
||||||
st.set_vfs_file_content(uri, params.text_document.text)?;
|
st.set_vfs_file_content(uri, params.text_document.text)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?
|
})?
|
||||||
@ -315,34 +334,9 @@ impl Server {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_config(&mut self, mut v: serde_json::Value) {
|
fn update_config(&mut self, value: serde_json::Value) {
|
||||||
let mut updated_diagnostics = false;
|
|
||||||
let mut config = Config::clone(&self.config);
|
let mut config = Config::clone(&self.config);
|
||||||
let mut errors = Vec::new();
|
let (errors, updated_diagnostics) = config.update(value);
|
||||||
if let Some(v) = v.pointer_mut("/diagnostics/ignored") {
|
|
||||||
match serde_json::from_value(v.take()) {
|
|
||||||
Ok(v) => {
|
|
||||||
config.diagnostics_ignored = v;
|
|
||||||
updated_diagnostics = true;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
errors.push(format!("Invalid value of `diagnostics.ignored`: {e}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(v) = v.pointer_mut("/formatting/command") {
|
|
||||||
match serde_json::from_value::<Option<Vec<String>>>(v.take()) {
|
|
||||||
Ok(Some(v)) if v.is_empty() => {
|
|
||||||
errors.push("`formatting.command` must not be an empty list".into());
|
|
||||||
}
|
|
||||||
Ok(v) => {
|
|
||||||
config.formatting_command = v;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
errors.push(format!("Invalid value of `formatting.command`: {e}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracing::debug!("Updated config, errors: {errors:?}, config: {config:?}");
|
tracing::debug!("Updated config, errors: {errors:?}, config: {config:?}");
|
||||||
self.config = Arc::new(config);
|
self.config = Arc::new(config);
|
||||||
|
|
||||||
@ -356,26 +350,41 @@ impl Server {
|
|||||||
|
|
||||||
// Refresh all diagnostics since the filter may be changed.
|
// Refresh all diagnostics since the filter may be changed.
|
||||||
if updated_diagnostics {
|
if updated_diagnostics {
|
||||||
for uri in &self.opened_files {
|
let version = self.next_version();
|
||||||
tracing::debug!("Recalculate diagnostics of {uri}");
|
for uri in self.opened_files.keys() {
|
||||||
self.update_diagnostics(uri.clone());
|
tracing::trace!("Recalculate diagnostics of {uri}, version {version}");
|
||||||
|
self.update_diagnostics(uri.clone(), version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_diagnostics(&self, uri: Url) {
|
fn update_diagnostics(&self, uri: Url, version: u64) {
|
||||||
let snap = self.snapshot();
|
let snap = self.snapshot();
|
||||||
let task = move || {
|
let task = move || {
|
||||||
let diags = with_catch_unwind("diagnostics", || handler::diagnostics(snap, &uri))
|
// Return empty diagnostics for ignored files.
|
||||||
.unwrap_or_else(|err| {
|
let diagnostics = (!snap.config.diagnostics_excluded_files.contains(&uri))
|
||||||
tracing::error!("Failed to calculate diagnostics: {err}");
|
.then(|| {
|
||||||
Vec::new()
|
with_catch_unwind("diagnostics", || handler::diagnostics(snap, &uri))
|
||||||
});
|
.unwrap_or_else(|err| {
|
||||||
Event::Diagnostics(uri, diags)
|
tracing::error!("Failed to calculate diagnostics: {err}");
|
||||||
|
Vec::new()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
Event::Diagnostics {
|
||||||
|
uri,
|
||||||
|
version,
|
||||||
|
diagnostics,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.task_tx.send(Box::new(task)).unwrap();
|
self.task_tx.send(Box::new(task)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_version(&mut self) -> u64 {
|
||||||
|
self.version_counter += 1;
|
||||||
|
self.version_counter
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot(&self) -> StateSnapshot {
|
fn snapshot(&self) -> StateSnapshot {
|
||||||
StateSnapshot {
|
StateSnapshot {
|
||||||
analysis: self.host.snapshot(),
|
analysis: self.host.snapshot(),
|
||||||
@ -399,20 +408,25 @@ impl Server {
|
|||||||
// Must be called without holding the lock of `vfs`.
|
// Must be called without holding the lock of `vfs`.
|
||||||
self.host.apply_change(changes);
|
self.host.apply_change(changes);
|
||||||
|
|
||||||
|
let version = self.next_version();
|
||||||
let vfs = self.vfs.read().unwrap();
|
let vfs = self.vfs.read().unwrap();
|
||||||
for (file, text) in file_changes {
|
for (file, text) in file_changes {
|
||||||
let uri = vfs.uri_for_file(file);
|
let uri = vfs.uri_for_file(file);
|
||||||
if !self.opened_files.contains(&uri) {
|
if !self.opened_files.contains_key(&uri) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Removed or closed files are indistinguishable from empty files.
|
// FIXME: Removed or closed files are indistinguishable from empty files.
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
self.update_diagnostics(uri);
|
self.update_diagnostics(uri, version);
|
||||||
} else {
|
} else {
|
||||||
// Clear diagnostics.
|
// Clear diagnostics.
|
||||||
self.event_tx
|
self.event_tx
|
||||||
.send(Event::Diagnostics(uri, Vec::new()))
|
.send(Event::Diagnostics {
|
||||||
|
uri,
|
||||||
|
version,
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ impl Vfs {
|
|||||||
// This is not quite efficient, but we already do many O(n) traversals.
|
// This is not quite efficient, but we already do many O(n) traversals.
|
||||||
let (new_text, line_map) = LineMap::normalize(new_text).ok_or("File too large")?;
|
let (new_text, line_map) = LineMap::normalize(new_text).ok_or("File too large")?;
|
||||||
let new_text = <Arc<str>>::from(new_text);
|
let new_text = <Arc<str>>::from(new_text);
|
||||||
log::debug!("File {:?} content changed: {:?}", file, new_text);
|
log::trace!("File {:?} content changed: {:?}", file, new_text);
|
||||||
self.files[file.0 as usize] = (new_text.clone(), Arc::new(line_map));
|
self.files[file.0 as usize] = (new_text.clone(), Arc::new(line_map));
|
||||||
self.change.change_file(file, new_text);
|
self.change.change_file(file, new_text);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -94,6 +94,7 @@ let
|
|||||||
settings.nil = {
|
settings.nil = {
|
||||||
testSetting = 42;
|
testSetting = 42;
|
||||||
formatting.command = [ "${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt" ];
|
formatting.command = [ "${pkgs.nixpkgs-fmt}/bin/nixpkgs-fmt" ];
|
||||||
|
diagnostics.excludedFiles = [ "generated.nix" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
45
docs/configuration.md
Normal file
45
docs/configuration.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
## LSP Configuration
|
||||||
|
|
||||||
|
There are some tunable options and settings for `nil`.
|
||||||
|
They are retrieved via LSP and support runtine modification.
|
||||||
|
|
||||||
|
All settings are nested under a key `"nil"`.
|
||||||
|
For example, `formatting.command` means to write
|
||||||
|
`"nil": { "formatting": { "command": ["your-command"] } }`
|
||||||
|
in JSON, not `"nil.formatting.command": ["wrong"]`.
|
||||||
|
|
||||||
|
The place to write LSP configurations differs between clients.
|
||||||
|
Please check the documentation of your LSP client (usually the editor or editor plugins).
|
||||||
|
There are some examples for common editor/plugins in [README](../README.md).
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
The values shown here are the default values.
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"nil": {
|
||||||
|
"formatting": {
|
||||||
|
// External formatter command (with arguments).
|
||||||
|
// It should accepts file content in stdin and print the formatted code into stdout.
|
||||||
|
// Type: [string] | null
|
||||||
|
// Example: ["nixpkgs-fmt"]
|
||||||
|
"command": null,
|
||||||
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
// Ignored diagnostic kinds.
|
||||||
|
// The kind identifier is a snake_cased_string usually shown together
|
||||||
|
// with the diagnostic message.
|
||||||
|
// Type: [string]
|
||||||
|
// Example: ["unused_binding", "unused_with"]
|
||||||
|
"ignored": [],
|
||||||
|
// Files to exclude from showing diagnostics. Useful for generated files.
|
||||||
|
// It accepts an array of paths. Relative paths are joint to the workspace root.
|
||||||
|
// Glob patterns are currently not supported.
|
||||||
|
// Type: [string]
|
||||||
|
// Example: ["Cargo.nix"]
|
||||||
|
"excludedFiles": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
@ -32,6 +32,7 @@ This incomplete list tracks noteble features currently implemented or planned.
|
|||||||
- [ ] Attrset fields.
|
- [ ] Attrset fields.
|
||||||
|
|
||||||
- [x] Diagnostics. `textDocument/publishDiagnostics`
|
- [x] Diagnostics. `textDocument/publishDiagnostics`
|
||||||
|
|
||||||
- [x] Syntax errors.
|
- [x] Syntax errors.
|
||||||
- [x] Hard semantic errors reported as parse errors by Nix, like duplicated keys in attrsets.
|
- [x] Hard semantic errors reported as parse errors by Nix, like duplicated keys in attrsets.
|
||||||
- [x] Undefiend names.
|
- [x] Undefiend names.
|
||||||
@ -39,13 +40,11 @@ This incomplete list tracks noteble features currently implemented or planned.
|
|||||||
- [x] Warnings of unnecessary syntax.
|
- [x] Warnings of unnecessary syntax.
|
||||||
- [x] Warnings of unused bindings, `with` and `rec`.
|
- [x] Warnings of unused bindings, `with` and `rec`.
|
||||||
- [ ] Client pulled diagnostics.
|
- [ ] Client pulled diagnostics.
|
||||||
- [x] Custom filter
|
- [x] Custom filter on kinds.
|
||||||
- You can disable some diagnostic messages via LSP setting `diagnostics.ignored`,
|
- [x] Exclude files.
|
||||||
which accepts an array of ignored diagnostic code strings,
|
|
||||||
eg. `["unused_binding","unused_with"]`.
|
|
||||||
The code of diagnostics is usually shows in parentheses together with the message.
|
|
||||||
|
|
||||||
See documentations of your editor about how to set LSP settings.
|
You can disable some diagnostic kinds or for some (generated) files via LSP configuration.
|
||||||
|
See [docs/configuration.md](./configuration.md) for more information.
|
||||||
|
|
||||||
- [x] Expand selection. `textDocument/selectionRange`
|
- [x] Expand selection. `textDocument/selectionRange`
|
||||||
- [x] Renaming. `textDocument/renamme`, `textDocument/prepareRename`
|
- [x] Renaming. `textDocument/renamme`, `textDocument/prepareRename`
|
||||||
@ -80,22 +79,18 @@ This incomplete list tracks noteble features currently implemented or planned.
|
|||||||
- [ ] Range formatting.
|
- [ ] Range formatting.
|
||||||
- [ ] On-type formatting.
|
- [ ] On-type formatting.
|
||||||
- [x] External formatter.
|
- [x] External formatter.
|
||||||
- Currently, an external formatter must be configured via LSP setting
|
|
||||||
`formatting.command` to enable this functionality.
|
|
||||||
It accepts `null` for disabled, or an non-empty array for the formatting command,
|
|
||||||
eg. `["nixpkgs-fmt"]` for [nixpkgs-fmt].
|
|
||||||
The command must read Nix code from stdin and print the formatted code to stdout.
|
|
||||||
|
|
||||||
[nixpkgs-fmt]: https://github.com/nix-community/nixpkgs-fmt
|
External formatter must be manually configured to work.
|
||||||
|
See [docs/configuration.md](./configuration.md) for more information.
|
||||||
|
|
||||||
You might need to set other editor settings to enable format-on-save.
|
When formatter is configured, you can also enable format-on-save in your editor.
|
||||||
Like, for [`coc.nvim`],
|
Like, for [`coc.nvim`],
|
||||||
```jsonc
|
```jsonc
|
||||||
// coc-settings.json
|
// coc-settings.json
|
||||||
{
|
{
|
||||||
"coc.preferences.formatOnSaveFiletypes": ["nix"]
|
"coc.preferences.formatOnSaveFiletypes": ["nix"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] Cross-file analysis.
|
- [ ] Cross-file analysis.
|
||||||
- [x] Multi-threaded.
|
- [x] Multi-threaded.
|
||||||
|
Loading…
Reference in New Issue
Block a user