Rust LSP文本处理库lsp-textdocument的使用,实现高效语言服务器协议文本操作与解析

Rust LSP文本处理库lsp-textdocument的使用,实现高效语言服务器协议文本操作与解析

简介

在开发LSP服务时,可能无法舒适地管理文本文档。开发困难的原因主要有两个:

  1. 通常只提供URL变量,需要自己读取文件内容
  2. 需要将偏移量从字符串索引映射到文本维度坐标

通过监听来自LSP客户端的通知,lsp-textdocument可以帮助您自动管理文本文档。

这个crate基于vscode-languageserver-textdocument。

示例用法

基本用法

use lsp_textdocument::TextDocuments;

fn main() {
    let text_documents = TextDocument::new();
    ...

    let text = text_documents.get_document_content(&url, None);
}

完整示例

下面是一个更完整的示例,展示如何使用lsp-textdocument库来管理文档内容:

use lsp_textdocument::{TextDocuments, DocumentUri};
use lsp_types::{TextDocumentItem, TextDocumentContentChangeEvent};

fn main() {
    // 创建文档管理器
    let mut text_documents = TextDocuments::new();
    
    // 文档URI
    let uri = DocumentUri::from("file:///example.rs");
    
    // 创建新文档
    let doc_item = TextDocumentItem {
        uri: uri.clone(),
        language_id: "rust".to_string(),
        version: 1,
        text: "fn main() {\n    println!(\"Hello, world!\");\n}".to_string(),
    };
    
    // 添加文档到管理器
    text_documents.add_document(doc_item);
    
    // 获取文档内容
    if let Some(content) = text_documents.get_document_content(&uri, None) {
        println!("Document content: {}", content);
    }
    
    // 更新文档内容
    let change = TextDocumentContentChangeEvent {
        range: None,  // 全文档替换
        range_length: None,
        text: "fn main() {\n    println!(\\"Hello, LSP!\\");\n}".to_string(),
    };
    
    text_documents.update_document(&uri, 2, vec![change]);
    
    // 再次获取更新后的内容
    if let Some(content) = text_documents.get_document_content(&uri, None) {
        println!("Updated content: {}", content);
    }
    
    // 获取文档位置信息
    if let Some(doc) = text_documents.get_document(&uri) {
        // 将LSP位置转换为文档偏移量
        let position = lsp_types::Position {
            line: 1,
            character: 8,
        };
        let offset = doc.offset_of(position).unwrap();
        println!("Position offset: {}", offset);
        
        // 将偏移量转回位置
        let pos = doc.position_of(offset).unwrap();
        println!("Offset position: line {}, char {}", pos.line, pos.character);
    }
}

注意事项

  • 文本文档的position-encoding仅支持UTF-16编码

安装

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

cargo add lsp-textdocument

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

lsp-textdocument = "0.4.2"

文档

更多详细信息请参考官方文档

完整示例demo

基于上述内容,这里提供一个更完整的示例,展示如何在实际项目中使用lsp-textdocument库:

use lsp_textdocument::{TextDocuments, DocumentUri};
use lsp_types::{
    TextDocumentItem, 
    TextDocumentContentChangeEvent,
    Position,
    VersionedTextDocumentIdentifier
};

fn main() {
    // 初始化文档管理器
    let mut docs = TextDocuments::new();
    
    // 创建文档URI
    let uri = DocumentUri::from("file:///project/main.rs");
    
    // 准备初始文档内容
    let initial_content = r#"fn main() {
    // 欢迎消息
    println!("Welcome to LSP!");
}"#.to_string();
    
    // 创建文档项
    let document = TextDocumentItem {
        uri: uri.clone(),
        language_id: "rust".to_string(),
        version: 1,
        text: initial_content,
    };
    
    // 添加文档到管理器
    docs.add_document(document);
    
    // 示例1: 获取文档内容
    if let Some(content) = docs.get_document_content(&uri, None) {
        println!("Initial content:\n{}", content);
    }
    
    // 示例2: 更新文档内容
    let changes = vec![
        TextDocumentContentChangeEvent {
            range: None, // 全文档替换
            range_length: None,
            text: r#"fn main() {
    // 新的欢迎消息
    println!("Hello from LSP!");
}"#.to_string()
        }
    ];
    
    // 更新文档版本号为2
    docs.update_document(&uri, 2, changes);
    
    // 示例3: 位置转换
    if let Some(doc) = docs.get_document(&uri) {
        // 定义位置(第2行,第4个字符)
        let pos = Position { line: 1, character: 4 };
        
        // 转换为偏移量
        if let Ok(offset) = doc.offset_of(pos) {
            println!("Position {:?} → offset {}", pos, offset);
            
            // 从偏移量转回位置
            let restored_pos = doc.position_of(offset).unwrap();
            println!("Offset {} → position {:?}", offset, restored_pos);
        }
    }
    
    // 示例4: 获取文档元数据
    if let Some(doc) = docs.get_document(&uri) {
        let versioned_id = VersionedTextDocumentIdentifier {
            uri: uri.clone(),
            version: doc.version(),
        };
        println!("Document version: {}", versioned_id.version);
    }
}

这个完整示例展示了:

  1. 文档的初始化和添加
  2. 内容的获取和更新
  3. 位置与偏移量的相互转换
  4. 文档元数据的获取
  5. 版本控制的基本操作

所有操作都遵循LSP协议规范,确保与各种LSP客户端的兼容性。


1 回复

Rust LSP文本处理库lsp-textdocument使用指南

lsp-textdocument是一个用于处理语言服务器协议(LSP)中文本文档操作的Rust库,它提供了高效的方式来处理文本内容、位置转换和范围操作。

主要功能

  • 文本内容管理
  • 位置(行/列)与偏移量之间的转换
  • 文本范围操作
  • 变更事件处理
  • 支持增量更新

基本使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
lsp-textdocument = "0.3"
lsp-types = "0.94"  # 用于LSP相关类型

创建文本文档

use lsp_textdocument::{FullTextDocument, TextDocumentContentChangeEvent};
use lsp_types::TextDocumentItem;

let doc_item = TextDocumentItem {
    uri: Url::parse("file:///example.rs").unwrap(),
    language_id: "rust".to_string(),
    version: 1,
    text: "fn main() {\n    println!(\"Hello\");\n}".to_string(),
};

let doc = FullTextDocument::new(doc_item);

基本操作示例

// 获取文档内容
let content = doc.get_text();

// 获取总行数
let line_count = doc.line_count();

// 位置到偏移量转换
let pos = lsp_types::Position {
    line: 1,
    character: 4,
};
let offset = doc.offset_at_position(pos).unwrap();
println!("偏移量: {}", offset); // 输出: 16

// 偏移量到位置转换
let position = doc.position_at_offset(16).unwrap();
println!("位置: {:?}", position); // 输出: Position { line: 1, character: 4 }

// 获取特定行内容
let line_text = doc.line(1).unwrap();
println!("第2行: {}", line_text); // 输出: "    println!(\"Hello\");"

处理文本变更

// 创建变更事件
let change = TextDocumentContentChangeEvent {
    range: Some(lsp_types::Range {
        start: lsp_types::Position { line: 1, character: 4 },
        end: lsp_types::Position { line: 1, character: 9 },
    }),
    range_length: None,
    text: "writeln!".to_string(),
};

// 应用变更
doc.apply_content_changes(vec![change]).unwrap();

println!("变更后内容:\n{}", doc.get_text());
// 输出:
// fn main() {
//     writeln!("Hello");
// }

范围操作

// 获取文本范围
let range = lsp_types::Range {
    start: lsp_types::Position { line: 0, character: 3 },
    end: lsp_types::Position { line: 1, character: 4 },
};

let range_text = doc.get_text_range(range).unwrap();
println!("范围文本: {}", range_text); // 输出: "main() {\n    w"

高级用法

增量更新处理

let mut doc = FullTextDocument::new(doc_item);

// 模拟一系列增量变更
let changes = vec![
    TextDocumentContentChangeEvent {
        range: Some(lsp_types::Range {
            start: lsp_types::Position { line: 1, character: 15 },
            end: lsp_types::Position { line: 1, character: 16 },
        }),
        range_length: None,
        text: "\", world".to_string(),
    },
    TextDocumentContentChangeEvent {
        range: None,
        range_length: None,
        text: "fn main() {\n    println!(\"Hello, world\");\n}\n".to_string(),
    },
];

doc.apply_content_changes(changes).unwrap();

版本控制

// 更新文档版本
doc.update_version(2);

// 获取当前版本
println!("当前版本: {}", doc.version());

完整示例代码

use lsp_textdocument::FullTextDocument;
use lsp_types::{TextDocumentItem, Position, Range, TextDocumentContentChangeEvent};
use url::Url;

fn main() {
    // 创建新的文本文档
    let doc_item = TextDocumentItem {
        uri: Url::parse("file:///demo.rs").unwrap(),
        language_id: "rust".to_string(),
        version: 1,
        text: "fn greet() {\n    println!(\"Hi\");\n}".to_string(),
    };
    
    let mut doc = FullTextDocument::new(doc_item);
    
    // 基本操作
    println!("初始内容:\n{}", doc.get_text());
    println!("总行数: {}", doc.line_count());
    
    // 位置转换
    let pos = Position { line: 1, character: 4 };
    let offset = doc.offset_at_position(pos).unwrap();
    println!("位置 {:?} 的偏移量: {}", pos, offset);
    
    // 获取行内容
    println!("第2行内容: {}", doc.line(1).unwrap());
    
    // 应用文本变更
    let change = TextDocumentContentChangeEvent {
        range: Some(Range {
            start: Position { line: 1, character: 15 },
            end: Position { line: 1, character: 16 },
        }),
        range_length: None,
        text: "\", Rust开发者".to_string(),
    };
    
    doc.apply_content_changes(vec![change]).unwrap();
    println!("变更后内容:\n{}", doc.get_text());
    
    // 范围操作
    let range = Range {
        start: Position { line: 0, character: 3 },
        end: Position { line: 1, character: 5 },
    };
    println!("范围文本: {}", doc.get_text_range(range).unwrap());
    
    // 版本控制
    doc.update_version(2);
    println!("当前文档版本: {}", doc.version());
}

性能提示

  1. 对于大型文件,优先使用增量更新而非全量替换
  2. 批量处理多个变更事件比单独处理更高效
  3. 频繁的位置转换操作可以缓存结果

lsp-textdocument库为Rust语言服务器开发提供了强大的文本处理基础,能够高效地处理LSP协议中的各种文本操作需求。

回到顶部