mirror of
https://github.com/google/gn-language-server.git
synced 2025-12-23 12:26:43 +00:00
Experimental support of code lens
This commit is contained in:
parent
8cee5ab3b3
commit
ae8ad15785
8 changed files with 211 additions and 9 deletions
|
|
@ -45,4 +45,5 @@ impl Default for Configurations {
|
|||
pub struct ExperimentalConfigurations {
|
||||
pub undefined_variable_analysis: bool,
|
||||
pub workspace_symbols: bool,
|
||||
pub target_lens: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,4 +55,10 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
Error::General(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ use std::{
|
|||
use tokio::spawn;
|
||||
use tower_lsp::{
|
||||
lsp_types::{
|
||||
CompletionOptions, CompletionParams, CompletionResponse, DidChangeConfigurationParams,
|
||||
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
|
||||
DocumentFormattingParams, DocumentLink, DocumentLinkOptions, DocumentLinkParams,
|
||||
DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse,
|
||||
Hover, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
|
||||
InitializedParams, Location, MessageType, OneOf, ReferenceParams, ServerCapabilities,
|
||||
SymbolInformation, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url,
|
||||
WorkspaceSymbolParams,
|
||||
CodeLens, CodeLensOptions, CodeLensParams, CompletionOptions, CompletionParams,
|
||||
CompletionResponse, DidChangeConfigurationParams, DidChangeTextDocumentParams,
|
||||
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentFormattingParams,
|
||||
DocumentLink, DocumentLinkOptions, DocumentLinkParams, DocumentSymbolParams,
|
||||
DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams,
|
||||
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, Location,
|
||||
MessageType, OneOf, ReferenceParams, ServerCapabilities, SymbolInformation,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, WorkspaceSymbolParams,
|
||||
},
|
||||
LanguageServer, LspService, Server,
|
||||
};
|
||||
|
|
@ -168,6 +168,9 @@ impl LanguageServer for Backend {
|
|||
document_formatting_provider: Some(OneOf::Left(true)),
|
||||
references_provider: Some(OneOf::Left(true)),
|
||||
workspace_symbol_provider: Some(OneOf::Left(true)),
|
||||
code_lens_provider: Some(CodeLensOptions {
|
||||
resolve_provider: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
|
@ -265,6 +268,14 @@ impl LanguageServer for Backend {
|
|||
) -> RpcResult<Option<Vec<SymbolInformation>>> {
|
||||
Ok(providers::workspace_symbol::workspace_symbol(&self.context.request(), params).await?)
|
||||
}
|
||||
|
||||
async fn code_lens(&self, params: CodeLensParams) -> RpcResult<Option<Vec<CodeLens>>> {
|
||||
Ok(providers::code_lens::code_lens(&self.context.request(), params).await?)
|
||||
}
|
||||
|
||||
async fn code_lens_resolve(&self, partial_lens: CodeLens) -> RpcResult<CodeLens> {
|
||||
Ok(providers::code_lens::code_lens_resolve(&self.context.request(), partial_lens).await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
|
|
|
|||
143
src/server/providers/code_lens.rs
Normal file
143
src/server/providers/code_lens.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde_json::Value;
|
||||
use tower_lsp::lsp_types::{CodeLens, CodeLensParams, Command, Position};
|
||||
|
||||
use crate::{
|
||||
common::error::Result,
|
||||
server::{
|
||||
providers::{
|
||||
references::target_references,
|
||||
utils::{format_path, get_text_document_path},
|
||||
},
|
||||
RequestContext,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum CodeLensData {
|
||||
TargetReferences(CodeLensDataTargetReferences),
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct CodeLensDataTargetReferences {
|
||||
pub path: PathBuf,
|
||||
pub position: Position,
|
||||
pub target_name: String,
|
||||
}
|
||||
|
||||
pub async fn code_lens(
|
||||
context: &RequestContext,
|
||||
params: CodeLensParams,
|
||||
) -> Result<Option<Vec<CodeLens>>> {
|
||||
if !context
|
||||
.client
|
||||
.configurations()
|
||||
.await
|
||||
.experimental
|
||||
.target_lens
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let path = get_text_document_path(¶ms.text_document)?;
|
||||
let current_file = context.analyzer.analyze_file(&path, context.request_time)?;
|
||||
|
||||
Ok(Some(
|
||||
current_file
|
||||
.analyzed_root
|
||||
.targets()
|
||||
.flat_map(|target| {
|
||||
let range = current_file.document.line_index.range(target.call.span);
|
||||
let label = format!(
|
||||
"{}:{}",
|
||||
format_path(
|
||||
current_file.document.path.parent().unwrap(),
|
||||
¤t_file.workspace_root
|
||||
),
|
||||
target.name
|
||||
);
|
||||
let position = current_file
|
||||
.document
|
||||
.line_index
|
||||
.position(target.call.span.start());
|
||||
[
|
||||
CodeLens {
|
||||
range,
|
||||
command: None,
|
||||
data: Some(
|
||||
serde_json::to_value(CodeLensData::TargetReferences(
|
||||
CodeLensDataTargetReferences {
|
||||
path: path.clone(),
|
||||
position,
|
||||
target_name: target.name.to_string(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
},
|
||||
CodeLens {
|
||||
range,
|
||||
command: Some(Command {
|
||||
title: "Copy".to_string(),
|
||||
command: "gn.copyTargetLabel".to_string(),
|
||||
arguments: Some(vec![Value::String(label)]),
|
||||
}),
|
||||
data: None,
|
||||
},
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn code_lens_resolve(
|
||||
context: &RequestContext,
|
||||
partial_lens: CodeLens,
|
||||
) -> Result<CodeLens> {
|
||||
let data = serde_json::from_value::<CodeLensData>(partial_lens.data.unwrap())?;
|
||||
match data {
|
||||
CodeLensData::TargetReferences(CodeLensDataTargetReferences {
|
||||
path,
|
||||
position,
|
||||
target_name,
|
||||
}) => {
|
||||
let current_file = context.analyzer.analyze_file(&path, context.request_time)?;
|
||||
let references = target_references(context, ¤t_file, &target_name)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let title = match references.len() {
|
||||
0 => "No references".to_string(),
|
||||
1 => "1 reference".to_string(),
|
||||
n => format!("{n} references"),
|
||||
};
|
||||
Ok(CodeLens {
|
||||
range: partial_lens.range,
|
||||
command: Some(Command {
|
||||
command: "gn.showTargetReferences".to_string(),
|
||||
title,
|
||||
arguments: Some(vec![
|
||||
serde_json::to_value(position).unwrap(),
|
||||
serde_json::to_value(references).unwrap(),
|
||||
]),
|
||||
}),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod code_lens;
|
||||
pub mod completion;
|
||||
pub mod configuration;
|
||||
pub mod diagnostics;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fn get_overlapping_targets<'i>(root: &AnalyzedBlock<'i, '_>, prefix: &str) -> Ve
|
|||
.collect()
|
||||
}
|
||||
|
||||
async fn target_references(
|
||||
pub async fn target_references(
|
||||
context: &RequestContext,
|
||||
current_file: &AnalyzedFile,
|
||||
target_name: &str,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@
|
|||
"default": true,
|
||||
"description": "Reports syntax errors."
|
||||
},
|
||||
"gn.experimental.targetLens": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enables code lens for build targets (experimental)."
|
||||
},
|
||||
"gn.experimental.undefinedVariableAnalysis": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as p2c from 'vscode-languageclient/lib/common/protocolConverter';
|
||||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
Location,
|
||||
MessageSignature,
|
||||
Position,
|
||||
ResponseError,
|
||||
ServerOptions,
|
||||
TransportKind,
|
||||
|
|
@ -126,6 +129,29 @@ async function openBuildFile(): Promise<void> {
|
|||
);
|
||||
}
|
||||
|
||||
async function showTargetReferences(
|
||||
converter: p2c.Converter,
|
||||
position: Position,
|
||||
locations: Location[]
|
||||
): Promise<void> {
|
||||
const documentUri = vscode.window.activeTextEditor?.document?.uri;
|
||||
if (!documentUri) {
|
||||
void vscode.window.showErrorMessage('No open editor.');
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.commands.executeCommand(
|
||||
'editor.action.showReferences',
|
||||
documentUri,
|
||||
converter.asPosition(position),
|
||||
locations.map(converter.asLocation)
|
||||
);
|
||||
}
|
||||
|
||||
async function copyTargetLabel(label: string): Promise<void> {
|
||||
await vscode.env.clipboard.writeText(label);
|
||||
}
|
||||
|
||||
class GnLanguageClient extends LanguageClient {
|
||||
constructor(context: vscode.ExtensionContext, output: vscode.OutputChannel) {
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
|
|
@ -189,6 +215,15 @@ async function startLanguageServer(
|
|||
const client = new GnLanguageClient(context, output);
|
||||
context.subscriptions.push(client);
|
||||
await client.start();
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
'gn.showTargetReferences',
|
||||
(position: Position, locations: Location[]) =>
|
||||
showTargetReferences(client.protocol2CodeConverter, position, locations)
|
||||
),
|
||||
vscode.commands.registerCommand('gn.copyTargetLabel', copyTargetLabel),
|
||||
);
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue