Skip to content

Commit 1930289

Browse files
committed
Add Config struct and move it from State to an RwLock in LanguageClient (autozimu#1156)
1 parent 23913c2 commit 1930289

File tree

7 files changed

+519
-526
lines changed

7 files changed

+519
-526
lines changed

src/config.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
use crate::{
2+
types::{
3+
CodeLensDisplay, DiagnosticsDisplay, DiagnosticsList, DocumentHighlightDisplay,
4+
HoverPreviewOption, RootMarkers, SelectionUI, UseVirtualText,
5+
},
6+
vim::Vim,
7+
};
8+
use anyhow::{anyhow, Result};
9+
use lsp_types::{DiagnosticSeverity, MarkupKind, MessageType, TraceOption};
10+
use serde::Deserialize;
11+
use std::collections::HashMap;
12+
use std::{path::PathBuf, str::FromStr, time::Duration};
13+
14+
#[derive(Debug)]
15+
pub struct Config {
16+
pub auto_start: bool,
17+
pub server_commands: HashMap<String, Vec<String>>,
18+
pub selection_ui: SelectionUI,
19+
pub trace: TraceOption,
20+
pub settings_path: Vec<String>,
21+
pub load_settings: bool,
22+
pub root_markers: Option<RootMarkers>,
23+
pub change_throttle: Option<Duration>,
24+
pub wait_output_timeout: Duration,
25+
pub diagnostics_enable: bool,
26+
pub diagnostics_list: DiagnosticsList,
27+
pub diagnostics_display: HashMap<u64, DiagnosticsDisplay>,
28+
pub code_lens_display: CodeLensDisplay,
29+
pub window_log_message_level: MessageType,
30+
pub hover_preview: HoverPreviewOption,
31+
pub completion_prefer_text_edit: bool,
32+
pub is_nvim: bool,
33+
pub logging_file: Option<PathBuf>,
34+
pub logging_level: log::LevelFilter,
35+
pub server_stderr: Option<String>,
36+
pub diagnostics_signs_max: Option<usize>,
37+
pub diagnostics_max_severity: DiagnosticSeverity,
38+
pub diagnostics_ignore_sources: Vec<String>,
39+
pub document_highlight_display: HashMap<u64, DocumentHighlightDisplay>,
40+
pub selection_ui_auto_open: bool,
41+
pub use_virtual_text: UseVirtualText,
42+
pub echo_project_root: bool,
43+
pub semantic_highlight_maps: HashMap<String, HashMap<String, String>>,
44+
pub semantic_scope_separator: String,
45+
pub apply_completion_text_edits: bool,
46+
pub preferred_markup_kind: Option<Vec<MarkupKind>>,
47+
pub hide_virtual_texts_on_insert: bool,
48+
pub enable_extensions: Option<HashMap<String, bool>>,
49+
pub restart_on_crash: bool,
50+
pub max_restart_retries: u8,
51+
}
52+
53+
impl Default for Config {
54+
fn default() -> Self {
55+
Self {
56+
server_commands: HashMap::new(),
57+
semantic_highlight_maps: HashMap::new(),
58+
semantic_scope_separator: ":".into(),
59+
auto_start: true,
60+
selection_ui: SelectionUI::LocationList,
61+
selection_ui_auto_open: true,
62+
trace: TraceOption::default(),
63+
diagnostics_enable: true,
64+
diagnostics_list: DiagnosticsList::Quickfix,
65+
diagnostics_display: DiagnosticsDisplay::default(),
66+
code_lens_display: CodeLensDisplay::default(),
67+
diagnostics_signs_max: None,
68+
diagnostics_max_severity: DiagnosticSeverity::Hint,
69+
diagnostics_ignore_sources: vec![],
70+
document_highlight_display: DocumentHighlightDisplay::default(),
71+
window_log_message_level: MessageType::Warning,
72+
settings_path: vec![format!(".vim{}settings.json", std::path::MAIN_SEPARATOR)],
73+
load_settings: false,
74+
root_markers: None,
75+
change_throttle: None,
76+
wait_output_timeout: Duration::from_secs(10),
77+
hover_preview: HoverPreviewOption::default(),
78+
completion_prefer_text_edit: false,
79+
apply_completion_text_edits: true,
80+
use_virtual_text: UseVirtualText::All,
81+
hide_virtual_texts_on_insert: true,
82+
echo_project_root: true,
83+
server_stderr: None,
84+
preferred_markup_kind: None,
85+
enable_extensions: None,
86+
is_nvim: false,
87+
logging_file: None,
88+
logging_level: log::LevelFilter::Off,
89+
restart_on_crash: true,
90+
max_restart_retries: 5,
91+
}
92+
}
93+
}
94+
95+
#[derive(Deserialize)]
96+
struct DeserializableConfig {
97+
logging_file: Option<PathBuf>,
98+
logging_level: log::LevelFilter,
99+
server_stderr: Option<String>,
100+
auto_start: u8,
101+
server_commands: HashMap<String, Vec<String>>,
102+
selection_ui: Option<String>,
103+
trace: Option<String>,
104+
settings_path: Vec<String>,
105+
load_settings: u8,
106+
root_markers: Option<RootMarkers>,
107+
change_throttle: Option<f64>,
108+
wait_output_timeout: Option<f64>,
109+
diagnostics_enable: u8,
110+
diagnostics_list: Option<String>,
111+
diagnostics_display: HashMap<u64, DiagnosticsDisplay>,
112+
window_log_message_level: String,
113+
hover_preview: Option<String>,
114+
completion_prefer_text_edit: u8,
115+
is_nvim: u8,
116+
diagnostics_signs_max: Option<usize>,
117+
diagnostics_max_severity: String,
118+
diagnostics_ignore_sources: Vec<String>,
119+
document_highlight_display: Option<HashMap<u64, DocumentHighlightDisplay>>,
120+
selection_ui_auto_open: u8,
121+
use_virtual_text: UseVirtualText,
122+
echo_project_root: u8,
123+
semantic_highlight_maps: HashMap<String, HashMap<String, String>>,
124+
semantic_scope_separator: String,
125+
apply_completion_text_edits: u8,
126+
preferred_markup_kind: Option<Vec<MarkupKind>>,
127+
hide_virtual_texts_on_insert: u8,
128+
enable_extensions: Option<HashMap<String, bool>>,
129+
code_lens_display: Option<CodeLensDisplay>,
130+
restart_on_crash: u8,
131+
max_restart_retries: u8,
132+
}
133+
134+
impl Config {
135+
pub fn parse(vim: Vim) -> Result<Self> {
136+
let req = r#"{
137+
"auto_start": !!get(g:, 'LanguageClient_autoStart', 1),
138+
"server_commands": s:GetVar('LanguageClient_serverCommands', {}),
139+
"selection_ui": s:getSelectionUI(),
140+
"trace": get(g:, 'LanguageClient_trace', v:null),
141+
"settings_path": map(s:ToList(get(g:, 'LanguageClient_settingsPath', '.vim/settings.json')), 'expand(v:val)'),
142+
"load_settings": !!get(g:, 'LanguageClient_loadSettings', 1),
143+
"root_markers": get(g:, 'LanguageClient_rootMarkers', v:null),
144+
"change_throttle": get(g:, 'LanguageClient_changeThrottle', v:null),
145+
"wait_output_timeout": get(g:, 'LanguageClient_waitOutputTimeout', v:null),
146+
"diagnostics_enable": !!get(g:, 'LanguageClient_diagnosticsEnable', 1),
147+
"diagnostics_list": get(g:, 'LanguageClient_diagnosticsList', 'Quickfix'),
148+
"diagnostics_display": get(g:, 'LanguageClient_diagnosticsDisplay', {}),
149+
"window_log_message_level": get(g:, 'LanguageClient_windowLogMessageLevel', 'Warning'),
150+
"hover_preview": get(g:, 'LanguageClient_hoverPreview', 'Auto'),
151+
"completion_prefer_text_edit": get(g:, 'LanguageClient_completionPreferTextEdit', 0),
152+
"is_nvim": has('nvim'),
153+
"diagnostics_signs_max": get(g:, 'LanguageClient_diagnosticsSignsMax', v:null),
154+
"diagnostics_max_severity": get(g:, 'LanguageClient_diagnosticsMaxSeverity', 'Hint'),
155+
"diagnostics_ignore_sources": get(g:, 'LanguageClient_diagnosticsIgnoreSources', []),
156+
"document_highlight_display": get(g:, 'LanguageClient_documentHighlightDisplay', {}),
157+
"selection_ui_auto_open": !!s:GetVar('LanguageClient_selectionUI_autoOpen', 1),
158+
"use_virtual_text": s:useVirtualText(),
159+
"echo_project_root": !!s:GetVar('LanguageClient_echoProjectRoot', 1),
160+
"semantic_highlight_maps": s:GetVar('LanguageClient_semanticHighlightMaps', {}),
161+
"semantic_scope_separator": s:GetVar('LanguageClient_semanticScopeSeparator', ':'),
162+
"apply_completion_text_edits": get(g:, 'LanguageClient_applyCompletionAdditionalTextEdits', 1),
163+
"preferred_markup_kind": get(g:, 'LanguageClient_preferredMarkupKind', v:null),
164+
"hide_virtual_texts_on_insert": s:GetVar('LanguageClient_hideVirtualTextsOnInsert', 0),
165+
"enable_extensions": get(g:, 'LanguageClient_enableExtensions', v:null),
166+
"code_lens_display": get(g:, 'LanguageClient_codeLensDisplay', v:null),
167+
"restart_on_crash": get(g:, 'LanguageClient_restartOnCrash', 1),
168+
"max_restart_retries": get(g:, 'LanguageClient_maxRestartRetries', 5),
169+
"logging_file": get(g:, 'LanguageClient_loggingFile', v:null),
170+
"logging_level": get(g:, 'LanguageClient_loggingLevel', 'WARN'),
171+
"server_stderr": get(g:, 'LanguageClient_serverStderr', v:null),
172+
}"#;
173+
174+
let res: DeserializableConfig = vim.eval(req.replace("\n", ""))?;
175+
176+
let loaded_fzf = vim.eval::<_, i64>("get(g:, 'loaded_fzf')")? == 1;
177+
let selection_ui = match res.selection_ui {
178+
Some(s) => SelectionUI::from_str(&s)?,
179+
None if loaded_fzf => SelectionUI::Funcref,
180+
None => SelectionUI::default(),
181+
};
182+
183+
let diagnostics_list = match res.diagnostics_list {
184+
Some(s) => DiagnosticsList::from_str(&s)?,
185+
None => DiagnosticsList::Disabled,
186+
};
187+
188+
let hover_preview = match res.hover_preview {
189+
Some(s) => HoverPreviewOption::from_str(&s)?,
190+
None => HoverPreviewOption::Auto,
191+
};
192+
193+
Ok(Config {
194+
auto_start: res.auto_start == 1,
195+
server_commands: res.server_commands,
196+
selection_ui,
197+
trace: trace(&res.trace.unwrap_or("off".to_string()))?,
198+
settings_path: res.settings_path,
199+
load_settings: res.load_settings == 1,
200+
root_markers: res.root_markers,
201+
change_throttle: res
202+
.change_throttle
203+
.map(|t| Duration::from_millis((t * 1000.0) as u64)),
204+
wait_output_timeout: Duration::from_millis(
205+
(res.wait_output_timeout.unwrap_or(10.0) * 1000.0) as u64,
206+
),
207+
diagnostics_enable: res.diagnostics_enable == 1,
208+
diagnostics_list,
209+
diagnostics_display: res.diagnostics_display,
210+
code_lens_display: res.code_lens_display.unwrap_or_default(),
211+
window_log_message_level: message_type(&res.window_log_message_level)?,
212+
hover_preview,
213+
completion_prefer_text_edit: res.completion_prefer_text_edit == 1,
214+
is_nvim: res.is_nvim == 1,
215+
logging_file: res.logging_file,
216+
logging_level: res.logging_level,
217+
server_stderr: res.server_stderr,
218+
diagnostics_signs_max: res.diagnostics_signs_max,
219+
diagnostics_max_severity: diagnostics_severity(&res.diagnostics_max_severity)?,
220+
diagnostics_ignore_sources: res.diagnostics_ignore_sources,
221+
document_highlight_display: res.document_highlight_display.unwrap_or_default(),
222+
selection_ui_auto_open: res.selection_ui_auto_open == 1,
223+
use_virtual_text: res.use_virtual_text,
224+
echo_project_root: res.echo_project_root == 1,
225+
semantic_highlight_maps: res.semantic_highlight_maps,
226+
semantic_scope_separator: res.semantic_scope_separator,
227+
apply_completion_text_edits: res.apply_completion_text_edits == 1,
228+
preferred_markup_kind: res.preferred_markup_kind,
229+
hide_virtual_texts_on_insert: res.hide_virtual_texts_on_insert == 1,
230+
enable_extensions: res.enable_extensions,
231+
restart_on_crash: res.restart_on_crash == 1,
232+
max_restart_retries: res.max_restart_retries,
233+
})
234+
}
235+
}
236+
237+
fn trace(s: &str) -> Result<TraceOption> {
238+
match s.to_ascii_uppercase().as_str() {
239+
"OFF" => Ok(TraceOption::Off),
240+
"MESSAGES" => Ok(TraceOption::Messages),
241+
"VERBOSE" => Ok(TraceOption::Verbose),
242+
_ => Err(anyhow!("Invalid option for LanguageClient_trace: {}", s)),
243+
}
244+
}
245+
246+
fn message_type(s: &str) -> Result<MessageType> {
247+
match s.to_ascii_uppercase().as_str() {
248+
"ERROR" => Ok(MessageType::Error),
249+
"WARNING" => Ok(MessageType::Warning),
250+
"INFO" => Ok(MessageType::Info),
251+
"LOG" => Ok(MessageType::Log),
252+
_ => Err(anyhow!(
253+
"Invalid option for LanguageClient_windowLogMessageLevel: {}",
254+
s,
255+
)),
256+
}
257+
}
258+
259+
fn diagnostics_severity(s: &str) -> Result<DiagnosticSeverity> {
260+
match s.to_ascii_uppercase().as_str() {
261+
"ERROR" => Ok(DiagnosticSeverity::Error),
262+
"WARNING" => Ok(DiagnosticSeverity::Warning),
263+
"INFORMATION" => Ok(DiagnosticSeverity::Information),
264+
"HINT" => Ok(DiagnosticSeverity::Hint),
265+
_ => Err(anyhow!(
266+
"Invalid option for LanguageClient_diagnosticsMaxSeverity: {}",
267+
s
268+
)),
269+
}
270+
}

src/language_client.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
use crate::{
2+
config::Config,
23
types::{LanguageId, State},
34
utils::diff_value,
45
vim::Vim,
56
};
67
use anyhow::{anyhow, Result};
78
use log::*;
89
use serde_json::Value;
9-
1010
use std::{
1111
collections::HashMap,
1212
ops::{Deref, DerefMut},
13-
sync::{Arc, Mutex, MutexGuard},
13+
sync::{Arc, Mutex, MutexGuard, RwLock},
1414
};
1515

1616
#[derive(Clone)]
1717
pub struct LanguageClient {
1818
version: String,
1919
state_mutex: Arc<Mutex<State>>,
2020
clients_mutex: Arc<Mutex<HashMap<LanguageId, Arc<Mutex<()>>>>>,
21+
config: Arc<RwLock<Config>>,
2122
}
2223

2324
impl LanguageClient {
@@ -26,6 +27,7 @@ impl LanguageClient {
2627
version,
2728
state_mutex: Arc::new(Mutex::new(state)),
2829
clients_mutex: Arc::new(Mutex::new(HashMap::new())),
30+
config: Arc::new(RwLock::new(Config::default())),
2931
}
3032
}
3133

@@ -62,11 +64,27 @@ impl LanguageClient {
6264
Ok(mutex)
6365
}
6466

65-
pub fn get<T>(&self, f: impl FnOnce(&State) -> T) -> Result<T> {
67+
pub fn get_config<K>(&self, f: impl FnOnce(&Config) -> K) -> Result<K> {
68+
Ok(f(self
69+
.config
70+
.read()
71+
.map_err(|err| anyhow!("Failed to lock config for reading: {:?}", err))?
72+
.deref()))
73+
}
74+
75+
pub fn update_config<K>(&self, f: impl FnOnce(&mut Config) -> K) -> Result<K> {
76+
Ok(f(self
77+
.config
78+
.write()
79+
.map_err(|err| anyhow!("Failed to lock config for writing: {:?}", err))?
80+
.deref_mut()))
81+
}
82+
83+
pub fn get_state<T>(&self, f: impl FnOnce(&State) -> T) -> Result<T> {
6684
Ok(f(self.lock()?.deref()))
6785
}
6886

69-
pub fn update<T>(&self, f: impl FnOnce(&mut State) -> Result<T>) -> Result<T> {
87+
pub fn update_state<T>(&self, f: impl FnOnce(&mut State) -> Result<T>) -> Result<T> {
7088
let mut state = self.lock()?;
7189
let mut state = state.deref_mut();
7290

@@ -93,6 +111,6 @@ impl LanguageClient {
93111
}
94112

95113
pub fn vim(&self) -> Result<Vim> {
96-
self.get(|state| state.vim.clone())
114+
self.get_state(|state| state.vim.clone())
97115
}
98116
}

0 commit comments

Comments
 (0)