Skip to content

Commit 1c7a3b0

Browse files
martskinsautozimu
authored andcommitted
Implement codeLens
1 parent 0394fba commit 1c7a3b0

File tree

7 files changed

+208
-37
lines changed

7 files changed

+208
-37
lines changed

autoload/LanguageClient.vim

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ endfunction
120120
function! s:useVirtualText() abort
121121
let l:use = s:GetVar('LanguageClient_useVirtualText')
122122
if l:use isnot v:null
123-
return !!l:use
123+
return l:use
124124
endif
125125

126126
return exists('*nvim_buf_set_virtual_text')
@@ -833,7 +833,7 @@ function! LanguageClient#workspace_symbol(...) abort
833833
return LanguageClient#Call('workspace/symbol', l:params, l:Callback)
834834
endfunction
835835

836-
function! LanguageClient#textDocument_codeAction(...) abort
836+
function! LanguageClient#textDocument_codeLens(...) abort
837837
let l:Callback = get(a:000, 1, v:null)
838838
let l:params = {
839839
\ 'filename': LSP#filename(),
@@ -843,6 +843,18 @@ function! LanguageClient#textDocument_codeAction(...) abort
843843
\ 'handle': s:IsFalse(l:Callback),
844844
\ }
845845
call extend(l:params, get(a:000, 0, {}))
846+
return LanguageClient#Call('textDocument/codeLens', l:params, l:Callback)
847+
endfunction
848+
849+
function! LanguageClient#textDocument_codeAction(...) abort
850+
let l:Callback = get(a:000, 1, v:null)
851+
let l:params = {
852+
\ 'filename': LSP#filename(),
853+
\ 'line': LSP#line(),
854+
\ 'character': LSP#character(),
855+
\ 'handle': s:IsFalse(l:Callback),
856+
\ }
857+
call extend(l:params, get(a:000, 0, {}))
846858
return LanguageClient#Call('textDocument/codeAction', l:params, l:Callback)
847859
endfunction
848860

@@ -1301,6 +1313,17 @@ function! LanguageClient#java_classFileContents(...) abort
13011313
return LanguageClient#Call('java/classFileContents', l:params, l:Callback)
13021314
endfunction
13031315

1316+
function! LanguageClient#codeLensAction(...) abort
1317+
let l:Callback = get(a:000, 1, v:null)
1318+
let l:params = {
1319+
\ 'filename': LSP#filename(),
1320+
\ 'line': LSP#line(),
1321+
\ 'character': LSP#character(),
1322+
\ }
1323+
call extend(l:params, get(a:000, 0, {}))
1324+
return LanguageClient#Call('LanguageClient_CodeLensAction', l:params, l:Callback)
1325+
endfunction
1326+
13041327
function! LanguageClient_contextMenuItems() abort
13051328
return {
13061329
\ 'Code Action': 'LanguageClient#textDocument_codeAction',

doc/LanguageClient.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ Default: >
344344

345345
Specify whether to use virtual text to display diagnostics.
346346

347-
Default: 1 whenever virtual text is supported.
348-
Valid Options: 1 | 0
347+
Default: "CodeLens" whenever virtual text is supported.
348+
Valid Options: "All" | "No" | "CodeLens" | "Diagnostics"
349349

350350
2.26 g:LanguageClient_useFloatingHover *g:LanguageClient_useFloatingHover*
351351

@@ -489,6 +489,19 @@ Example bindings combining with tpope/vim-abolish:
489489
\ {'newName': Abolish.uppercase(expand('<cword>'))})<CR>
490490
<
491491

492+
*LanguageClient#textDocument_codeLens()*
493+
*LanguageClient_textDocument_codeLens()*
494+
Signature: LanguageClient#textDocument_codeLens(...)
495+
496+
Computes and displays the codeLens for the currently open file.
497+
498+
*LanguageClient#textDocument_codeLensAction()*
499+
*LanguageClient_textDocument_codeLensAction()*
500+
Signature: LanguageClient#textDocument_codeLensAction(...)
501+
502+
Runs the action associated with the codeLens at the current line. If does
503+
nothing if the codeLens is not actionable.
504+
492505
*LanguageClient#textDocument_documentSymbol()*
493506
*LanguageClient_textDocument_documentSymbol()*
494507
Signature: LanguageClient#textDocument_documentSymbol(...)

plugin/LanguageClient.vim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ function! LanguageClient_textDocument_codeAction(...)
3434
return call('LanguageClient#textDocument_codeAction', a:000)
3535
endfunction
3636

37+
function! LanguageClient_textDocument_codeLens(...)
38+
return call('LanguageClient#textDocument_codeLens', a:000)
39+
endfunction
40+
3741
function! LanguageClient_textDocument_completion(...)
3842
return call('LanguageClient#textDocument_completion', a:000)
3943
endfunction

src/language_server_protocol.rs

Lines changed: 147 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl LanguageClient {
120120
selectionUI_autoOpen,
121121
use_virtual_text,
122122
echo_project_root,
123-
): (Option<u64>, String, Value, u8, u8, u8) = self.vim()?.eval(
123+
): (Option<u64>, String, Value, u8, UseVirtualText, u8) = self.vim()?.eval(
124124
[
125125
"get(g:, 'LanguageClient_diagnosticsSignsMax', v:null)",
126126
"get(g:, 'LanguageClient_diagnosticsMaxSeverity', 'Hint')",
@@ -226,7 +226,7 @@ impl LanguageClient {
226226
state.wait_output_timeout = wait_output_timeout;
227227
state.hoverPreview = hoverPreview;
228228
state.completionPreferTextEdit = completionPreferTextEdit;
229-
state.use_virtual_text = use_virtual_text == 1;
229+
state.use_virtual_text = use_virtual_text;
230230
state.echo_project_root = echo_project_root == 1;
231231
state.loggingFile = loggingFile;
232232
state.loggingLevel = loggingLevel;
@@ -850,6 +850,18 @@ impl LanguageClient {
850850
}
851851
}
852852
}
853+
"rust-analyzer.runSingle" | "rust-analyzer.run" => {
854+
if let Some(ref args) = cmd.arguments {
855+
let args = args[0].clone();
856+
let bin: String =
857+
try_get("bin", &args)?.ok_or_else(|| err_msg("no bin found"))?;
858+
let arguments: Vec<String> = try_get("args", &args)?.unwrap_or_else(|| vec![]);
859+
let cmd = format!("term {} {}", bin, arguments.join(" "));
860+
let cmd = cmd.replace('"', "");
861+
self.vim()?.command(cmd)?;
862+
}
863+
}
864+
// TODO: implement all other rust-analyzer actions
853865
_ => return Ok(false),
854866
}
855867

@@ -1000,6 +1012,9 @@ impl LanguageClient {
10001012
publish_diagnostics: Some(PublishDiagnosticsCapability {
10011013
related_information: Some(true),
10021014
}),
1015+
code_lens: Some(GenericCapability {
1016+
dynamic_registration: Some(true),
1017+
}),
10031018
..TextDocumentClientCapabilities::default()
10041019
}),
10051020
workspace: Some(WorkspaceClientCapabilities {
@@ -1759,6 +1774,74 @@ impl LanguageClient {
17591774
Ok(())
17601775
}
17611776

1777+
pub fn languageClient_handleCodeLensAction(&self, params: &Value) -> Fallible<Value> {
1778+
let filename = self.vim()?.get_filename(params)?;
1779+
let line = self.vim()?.get_position(params)?.line;
1780+
1781+
let mut code_lens: Vec<CodeLens> =
1782+
self.get(|state| state.code_lens.get(filename.as_str()).cloned().unwrap())?;
1783+
code_lens.retain(|cl| cl.range.start.line == line);
1784+
if code_lens.len() != 1 {
1785+
return Ok(Value::Null);
1786+
}
1787+
1788+
if let Some(command) = code_lens.pop().unwrap().command {
1789+
if !self.try_handle_command_by_client(&command)? {
1790+
let params = json!({
1791+
"command": command.command,
1792+
"arguments": command.arguments,
1793+
});
1794+
self.workspace_executeCommand(&params)?;
1795+
}
1796+
}
1797+
1798+
Ok(Value::Null)
1799+
}
1800+
1801+
pub fn textDocument_codeLens(&self, params: &Value) -> Fallible<Value> {
1802+
let use_virtual_text = self.get(|state| state.use_virtual_text.clone())?;
1803+
if UseVirtualText::No == use_virtual_text || UseVirtualText::Diagnostics == use_virtual_text
1804+
{
1805+
return Ok(Value::Null);
1806+
}
1807+
1808+
info!("Begin {}", lsp::request::CodeLensRequest::METHOD);
1809+
let filename = self.vim()?.get_filename(params)?;
1810+
let language_id = self.vim()?.get_languageId(&filename, params)?;
1811+
let client = self.get_client(&Some(language_id))?;
1812+
let input = lsp::CodeLensParams {
1813+
text_document: TextDocumentIdentifier {
1814+
uri: filename.to_url()?,
1815+
},
1816+
};
1817+
1818+
let results: Value = client.call(lsp::request::CodeLensRequest::METHOD, &input)?;
1819+
1820+
let code_lens: Option<Vec<CodeLens>> = serde_json::from_value(results.clone())?;
1821+
let mut resolved_code_lens = vec![];
1822+
if let Some(code_lens) = code_lens {
1823+
for item in code_lens {
1824+
let mut item = item;
1825+
if let Some(_d) = &item.data {
1826+
if let Some(cl) = client.call(lsp::request::CodeLensResolve::METHOD, &item)? {
1827+
item = cl;
1828+
}
1829+
}
1830+
resolved_code_lens.push(item);
1831+
}
1832+
}
1833+
1834+
self.update(|state| {
1835+
state
1836+
.code_lens
1837+
.insert(filename.to_owned(), resolved_code_lens);
1838+
Ok(Value::Null)
1839+
})?;
1840+
1841+
info!("End {}", lsp::request::CodeLensRequest::METHOD);
1842+
Ok(results)
1843+
}
1844+
17621845
pub fn textDocument_didOpen(&self, params: &Value) -> Fallible<()> {
17631846
info!("Begin {}", lsp::notification::DidOpenTextDocument::METHOD);
17641847
let filename = self.vim()?.get_filename(params)?;
@@ -1794,6 +1877,8 @@ impl LanguageClient {
17941877
.rpcclient
17951878
.notify("s:ExecuteAutocmd", "LanguageClientTextDocumentDidOpenPost")?;
17961879

1880+
self.textDocument_codeLens(params)?;
1881+
17971882
info!("End {}", lsp::notification::DidOpenTextDocument::METHOD);
17981883
Ok(())
17991884
}
@@ -1854,6 +1939,8 @@ impl LanguageClient {
18541939
},
18551940
)?;
18561941

1942+
self.textDocument_codeLens(params)?;
1943+
18571944
info!("End {}", lsp::notification::DidChangeTextDocument::METHOD);
18581945
Ok(())
18591946
}
@@ -2347,7 +2434,9 @@ impl LanguageClient {
23472434
if !self.get(|state| state.serverCommands.contains_key(&languageId))? {
23482435
return Ok(());
23492436
}
2350-
if !self.get(|state| state.diagnostics.contains_key(&filename))? {
2437+
if !self.get(|state| state.diagnostics.contains_key(&filename))?
2438+
&& !self.get(|state| state.code_lens.contains_key(&filename))?
2439+
{
23512440
return Ok(());
23522441
}
23532442

@@ -2500,40 +2589,67 @@ impl LanguageClient {
25002589
.notify("s:AddHighlights", json!([source, highlights]))?;
25012590
}
25022591

2503-
if self.get(|state| state.use_virtual_text)? {
2504-
let namespace_id = self.get_or_create_namespace()?;
2592+
let mut virtual_texts = vec![];
2593+
let use_virtual_text = self.get(|state| state.use_virtual_text.clone())?;
25052594

2506-
let mut virtual_texts = vec![];
2507-
self.update(|state| {
2508-
if let Some(diag_list) = state.diagnostics.get(&filename) {
2509-
for diag in diag_list {
2510-
if viewport.overlaps(diag.range) {
2511-
virtual_texts.push(VirtualText {
2512-
line: diag.range.start.line,
2513-
text: diag.message.replace("\n", " ").clone(),
2514-
hl_group: state
2515-
.diagnosticsDisplay
2516-
.get(
2517-
&(diag.severity.unwrap_or(DiagnosticSeverity::Hint) as u64),
2518-
)
2519-
.ok_or_else(|| err_msg("Failed to get display"))?
2520-
.virtualTexthl
2521-
.clone(),
2522-
});
2523-
}
2595+
// diagnostics
2596+
if UseVirtualText::All == use_virtual_text
2597+
|| UseVirtualText::Diagnostics == use_virtual_text
2598+
{
2599+
let diagnostics = self.get(|state| state.diagnostics.clone())?;
2600+
let diagnosticsDisplay = self.get(|state| state.diagnosticsDisplay.clone())?;
2601+
let diag_list = diagnostics.get(&filename);
2602+
if let Some(diag_list) = diag_list {
2603+
for diag in diag_list {
2604+
if viewport.overlaps(diag.range) {
2605+
virtual_texts.push(VirtualText {
2606+
line: diag.range.start.line,
2607+
text: diag.message.replace("\n", " ").clone(),
2608+
hl_group: diagnosticsDisplay
2609+
.get(&(diag.severity.unwrap_or(DiagnosticSeverity::Hint) as u64))
2610+
.ok_or_else(|| err_msg("Failed to get display"))?
2611+
.virtualTexthl
2612+
.clone(),
2613+
});
25242614
}
25252615
}
2526-
Ok(())
2616+
}
2617+
}
2618+
2619+
// code lens
2620+
if UseVirtualText::All == use_virtual_text || UseVirtualText::CodeLens == use_virtual_text {
2621+
let filename = self.vim()?.get_filename(params)?;
2622+
let code_lenses = self.get(|state| {
2623+
state
2624+
.code_lens
2625+
.get(&filename)
2626+
.cloned()
2627+
.unwrap_or_else(|| vec![])
25272628
})?;
2528-
self.vim()?.set_virtual_texts(
2529-
bufnr,
2530-
namespace_id,
2531-
viewport.start,
2532-
viewport.end,
2533-
&virtual_texts,
2534-
)?;
2629+
2630+
for cl in code_lenses {
2631+
if cl.command.is_none() {
2632+
continue;
2633+
}
2634+
let command = cl.command.unwrap();
2635+
2636+
virtual_texts.push(VirtualText {
2637+
line: cl.range.start.line,
2638+
text: command.title,
2639+
hl_group: "Comment".into(),
2640+
})
2641+
}
25352642
}
25362643

2644+
let namespace_id = self.get_or_create_namespace()?;
2645+
self.vim()?.set_virtual_texts(
2646+
bufnr,
2647+
namespace_id,
2648+
viewport.start,
2649+
viewport.end,
2650+
&virtual_texts,
2651+
)?;
2652+
25372653
info!("End {}", NOTIFICATION__HandleCursorMoved);
25382654
Ok(())
25392655
}

src/rpchandler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl LanguageClient {
7878
lsp::request::References::METHOD => self.textDocument_references(&params),
7979
lsp::request::Formatting::METHOD => self.textDocument_formatting(&params),
8080
lsp::request::RangeFormatting::METHOD => self.textDocument_rangeFormatting(&params),
81+
lsp::request::CodeLensRequest::METHOD => self.textDocument_codeLens(&params),
8182
lsp::request::ResolveCompletionItem::METHOD => self.completionItem_resolve(&params),
8283
lsp::request::ExecuteCommand::METHOD => self.workspace_executeCommand(&params),
8384
lsp::request::ApplyWorkspaceEdit::METHOD => self.workspace_applyEdit(&params),
@@ -98,6 +99,7 @@ impl LanguageClient {
9899
REQUEST__OmniComplete => self.languageClient_omniComplete(&params),
99100
REQUEST__ClassFileContents => self.java_classFileContents(&params),
100101
REQUEST__DebugInfo => self.debug_info(&params),
102+
REQUEST__CodeLensAction => self.languageClient_handleCodeLensAction(&params),
101103

102104
_ => {
103105
let languageId_target = if languageId.is_some() {

0 commit comments

Comments
 (0)