Rust语言服务器协议库codespan-lsp的使用,实现高效源代码分析与错误诊断

Rust语言服务器协议库codespan-lsp的使用,实现高效源代码分析与错误诊断

安装

在项目目录中运行以下Cargo命令:

cargo add codespan-lsp

或者在Cargo.toml中添加以下行:

codespan-lsp = "0.11.1"

基本使用示例

以下是使用codespan-lsp进行源代码分析和错误诊断的基本示例:

use codespan_lsp::{Diagnostic, DiagnosticSeverity, Position, Range};
use codespan::{FileId, Files};
use codespan_reporting::diagnostic::{Label, LabelStyle};

fn main() {
    // 创建源代码文件容器
    let mut files = Files::new();
    let file_id = files.add("example.rs", r#"
        fn main() {
            let x = 42;
            println!("{}", y);
        }
    "#);

    // 创建诊断信息
    let diagnostic = Diagnostic {
        range: Range {
            start: Position {
                line: 3,
                character: 20,
            },
            end: Position {
                line: 3,
                character: 21,
            },
        },
        severity: Some(DiagnosticSeverity::Error),
        code: Some("E0425".into()),
        code_description: None,
        source: Some("rustc".into()),
        message: "cannot find value `y` in this scope".into(),
        related_information: None,
        tags: None,
        data: None,
    };

    // 生成详细的错误标签
    let label = Label {
        style: LabelStyle::Primary,
        file_id,
        range: 70..71, // y的位置
        message: "not found in this scope".into(),
    };

    // 这里可以添加将诊断信息发送到LSP客户端的逻辑
    println!("Generated diagnostic: {:?}", diagnostic);
    println!("Generated label: {:?}", label);
}

完整示例

以下是一个更完整的示例,展示了如何设置LSP服务器并使用codespan-lsp进行源代码分析:

use codespan_lsp::{Server, Diagnostic, DiagnosticSeverity, Position, Range};
use codespan::{FileId, Files};
use lsp_types::{
    InitializeParams, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
};
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // 创建LSP服务器
    let (service, messages) = Server::new(|client| async move {
        // 服务器能力配置
        let capabilities = serde_json::to_value(ServerCapabilities {
            text_document_sync: Some(TextDocumentSyncCapability::Kind(
                TextDocumentSyncKind::FULL,
            )),
            ..Default::default()
        })
        .unwrap();

        // 处理初始化请求
        client
            .on_request::<lsp_types::request::Initialize, _>(move |params: InitializeParams| {
                Ok(serde_json::json!({
                    "capabilities": capabilities,
                    "serverInfo": {
                        "name": "codespan-lsp-example",
                        "version": "0.1.0"
                    }
                }))
            });

        // 处理文本文档诊断
        client.on_notification::<lsp_types::notification::DidOpenTextDocument>(|params| {
            let uri = params.text_document.uri;
            let text = params.text_document.text;
            
            // 创建源代码文件容器
            let mut files = Files::new();
            let file_id = files.add(uri.path(), &text);
            
            // 这里可以添加实际的代码分析逻辑
            // 示例: 创建一个假想的诊断信息
            let diagnostic = Diagnostic {
                range: Range {
                    start: Position { line: 0, character: 0 },
                    end: Position { line: 0, character: 5 },
                },
                severity: Some(DiagnosticSeverity::Warning),
                code: Some("EXAMPLE".into()),
                message: "This is an example diagnostic".into(),
                ..Default::default()
            };
            
            // 在实际应用中,这里会发送诊断信息给客户端
            println!("Generated diagnostic for {}: {:?}", uri, diagnostic);
            
            Ok(())
        });
    });

    // 运行服务器
    Server::run(service, messages).await?;
    
    Ok(())
}

关键功能

  1. 源代码定位:codespan-lsp提供了精确的源代码位置跟踪能力
  2. 诊断信息生成:可以生成符合LSP标准的诊断信息
  3. 错误报告:支持创建详细的错误标签和消息
  4. 与codespan集成:无缝对接codespan的源代码管理功能

1 回复

我将根据您提供的内容,整理关于codespan-lsp的完整示例demo。首先展示内容中已有的示例,然后提供一个更完整的实现。

已有示例代码回顾

基本语言服务器示例

use codespan_lsp::{
    server::{LanguageServer, Server},
    PositionEncoding,
};
use tower::Service;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let (service, messages) = LanguageServer::new(|client| Server {
        client,
        position_encoding: PositionEncoding::Utf8,
    });

    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();

    codespan_lsp::stdio::run(&mut service, &messages, stdin, stdout).await?;

    Ok(())
}

Rust语法检查器示例

use codespan_lsp::{
    server::{LanguageServer, Server},
    lsp_types::{
        Url, Diagnostic, DiagnosticSeverity, Position, Range,
        PublishDiagnosticsParams, DidOpenTextDocumentParams,
    },
};
use tower::Service;

struct RustLinter {
    documents: std::collections::HashMap<Url, String>,
}

impl Server for RustLinter {
    fn on_open_text_document(
        &mut self,
        params: DidOpenTextDocumentParams,
    ) -> Option<codespan_lsp::server::Request> {
        let uri = params.text_document.uri;
        let text = params.text_document.text;
        self.documents.insert(uri.clone(), text.clone());
        
        let diagnostics = if !text.contains("fn main()") {
            vec![Diagnostic {
                range: Range {
                    start: Position { line: 0, character: 0 },
                    end: Position { line: 0, character: 0 },
                },
                severity: Some(DiagnosticSeverity::Warning),
                message: "Rust file should contain a main function".to_string(),
                ..Default::default()
            }]
        } else {
            vec![]
        };
        
        Some(codespan_lsp::server::Request::PublishDiagnostics(
            PublishDiagnosticsParams {
                uri,
                diagnostics,
                version: None,
            }
        ))
    }
}

完整示例:增强型Rust语言服务器

下面是一个更完整的Rust语言服务器实现,包含语法检查、错误诊断和简单的代码补全功能:

use codespan_lsp::{
    server::{LanguageServer, Server},
    lsp_types::{
        Url, Diagnostic, DiagnosticSeverity, Position, Range,
        PublishDiagnosticsParams, DidOpenTextDocumentParams,
        DidChangeTextDocumentParams, CompletionParams, CompletionItem,
        CompletionResponse, InitializeParams,
    },
    PositionEncoding,
};
use tower::Service;
use std::collections::HashMap;

struct EnhancedRustServer {
    documents: HashMap<Url, String>,
    position_encoding: PositionEncoding,
}

impl Server for EnhancedRustServer {
    fn initialize(&mut self, params: InitializeParams) -> Option<codespan_lsp::server::Request> {
        // 设置位置编码
        self.position_encoding = params.capabilities.position_encoding
            .unwrap_or(PositionEncoding::Utf8);
        None
    }

    fn on_open_text_document(
        &mut self,
        params: DidOpenTextDocumentParams,
    ) -> Option<codespan_lsp::server::Request> {
        let uri = params.text_document.uri;
        let text = params.text_document.text;
        self.documents.insert(uri.clone(), text.clone());
        
        // 检查基本的Rust语法
        let mut diagnostics = Vec::new();
        
        if !text.contains("fn main()") {
            diagnostics.push(Diagnostic {
                range: Range {
                    start: Position { line: 0, character: 0 },
                    end: Position { line: 0, character: 0 },
                },
                severity: Some(DiagnosticSeverity::Warning),
                message: "Rust文件应该包含main函数".to_string(),
                ..Default::default()
            });
        }
        
        // 检查常见的语法错误
        if text.contains("let mut") && !text.contains("=") {
            diagnostics.push(Diagnostic {
                range: Range {
                    start: Position { line: 0, character: 0 },
                    end: Position { line: 0, character: 0 },
                },
                severity: Some(DiagnosticSeverity::Error),
                message: "可变绑定必须初始化".to_string(),
                ..Default::default()
            });
        }

        Some(codespan_lsp::server::Request::PublishDiagnostics(
            PublishDiagnosticsParams {
                uri,
                diagnostics,
                version: None,
            }
        ))
    }

    fn on_change_text_document(
        &mut self,
        params: DidChangeTextDocumentParams,
    ) -> Option<codespan_lsp::server::Request> {
        let uri = params.text_document.uri;
        if let Some(text) = params.content_changes.into_iter().next() {
            self.documents.insert(uri.clone(), text.text);
        }
        None
    }

    fn on_completion(
        &mut self,
        _params: CompletionParams,
    ) -> Option<codespan_lsp::server::Request> {
        // 提供简单的自动补全
        let items = vec![
            CompletionItem {
                label: "fn".to_string(),
                kind: Some(lsp_types::CompletionItemKind::KEYWORD),
                detail: Some("函数定义".to_string()),
                ..Default::default()
            },
            CompletionItem {
                label: "let".to_string(),
                kind: Some(lsp_types::CompletionItemKind::KEYWORD),
                detail: Some("变量绑定".to_string()),
                ..Default::default()
            },
            CompletionItem {
                label: "println!".to_string(),
                kind: Some(lsp_types::CompletionItemKind::FUNCTION),
                detail: Some("打印宏".to_string()),
                ..Default::default()
            },
        ];

        Some(codespan_lsp::server::Request::CompletionResponse(
            CompletionResponse::Array(items)
        ))
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 创建语言服务器实例
    let (mut service, messages) = LanguageServer::new(|client| EnhancedRustServer {
        documents: HashMap::new(),
        position_encoding: PositionEncoding::Utf8,
    });

    // 运行标准IO服务
    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();
    
    codespan_lsp::stdio::run(&mut service, &messages, stdin, stdout).await?;
    
    Ok(())
}

关键功能说明

  1. 初始化处理:通过initialize方法设置位置编码
  2. 文档管理:使用HashMap跟踪打开的文档内容
  3. 语法检查
    • 检查是否包含main函数
    • 验证可变绑定是否初始化
  4. 变更通知:处理文档内容变更
  5. 代码补全:提供基本的Rust关键字和宏补全

扩展建议

  1. 集成codespan诊断:可以结合codespan库提供更精确的错误定位
  2. 添加更多分析功能:如类型检查、借用检查等
  3. 支持更多LSP特性:如代码格式化、重命名等

这个完整示例展示了如何使用codespan-lsp构建一个功能相对完整的Rust语言服务器,可以作为开发更复杂语言工具的基础。

回到顶部