laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! # Text Document Position Index Storage Conventions
//!
//! The text document position index stores mappings from document positions to
//! symbols. Each position record stores a `ContentHash` referencing its
//! `DocumentSymbolRecord` in the document_symbols partition, plus a
//! `symbol_ident` for fast filtering without a second query.
//!
//! ## Partition Key Format
//!
//! ```text
//! text_document_position_index
//! ```
//!
//! All position index records use a single partition key
//! `laburnum_text_document_position_index`.
//!
//! Rationale:
//! - Enables fast position-to-symbol lookups for goto, hover, and rename
//!   operations
//! - File-level granularity provided by sort key prefix (source_key)
//! - SourceKey versioning automatically handles stale position data
//!
//! ## Sort Key Format
//!
//! ```text
//! {source_key}|{line:010}|{character:010}|{kind}
//! ```
//!
//! Components:
//! - `source_key`: SourceKey formatted as `{file_id}v{version}` (e.g., `42v5`)
//! - `line`: Line number as zero-padded 10-digit decimal integer
//! - `character`: Character position as zero-padded 10-digit decimal integer
//!   (UTF-16 code units per LSP spec)
//! - `kind`: PositionKind as single digit (0=Unknown, 1=Ident, 2=Symbol,
//!   3=Reference)
//!
//! Examples:
//! - `10v1|0000000000|0000000000|1` - Ident at line 0, char 0 in file_id=10,
//!   version=1
//! - `10v1|0000000000|0000000010|3` - Reference at line 0, char 10 in same file
//! - `10v1|0000000015|0000000025|2` - Symbol at line 15, char 25 in same file
//!
//! Rationale:
//! - SourceKey prefix enables efficient file-level queries
//! - Line component allows querying all positions on a specific line
//! - Zero-padded decimal ensures proper lexicographic sorting (5 < 10)
//! - Kind discriminator enables distinguishing idents from references (critical
//!   for rename)
//! - SourceKey includes version, so each file version gets its own position
//!   index
//!
//! ## Query Patterns
//!
//! ```rust,ignore
//! // Get symbol at specific position and kind
//! let position_key = text_document_position_sort_key(source_key, position, PositionKind::Ident);
//! let record = query.sort_key_equals(position_key);
//!
//! // Get all kinds at a specific position
//! let position_prefix = format!("{}|{}|{}|", source_key, position.line, position.character);
//! query.sort_key_begins_with(position_prefix)
//!
//! // Get all positions in a file
//! let file_prefix = format!("{}|", source_key);
//! query.sort_key_begins_with(file_prefix)
//!
//! // Get all positions on a specific line
//! let line_prefix = format!("{}|{}|", source_key, position.line);
//! query.sort_key_begins_with(line_prefix)
//! ```
//!
//! ## Implementation Notes
//!
//! Language implementers should:
//! 1. Create position index record types (e.g., `NanoTextDocumentPosition`)
//! 2. Store a `ContentHash` of the corresponding `DocumentSymbolRecord` and the
//!    symbol's `Ident` inline
//! 3. Implement `TextDocumentPositionRecord` trait
//! 4. Store positions using the sort key format above
//! 5. Populate the index during parsing or on-demand
//!
//! The full symbol data is resolved at query time via
//! `query_client.get_by_hash::<DocumentSymbols>(hash)`. The inline
//! `symbol_ident` enables fast filtering (e.g. rename loops) without
//! a second query.

use serde::{
  Deserialize,
  Serialize,
};

pub use crate::{
  partitions::text_document_position::PositionKind,
  protocol::lsp::Position,
};

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TextDocumentPositionParams {
  pub text_document: crate::protocol::lsp::TextDocumentIdentifier,
  pub position:      Position,
}

#[cfg(test)]
mod tests {
  use {
    super::*,
    crate::{
      SourceKey,
      partitions::text_document_position::{
        PositionKind,
        TextDocumentPositionSortKey,
      },
    },
  };

  #[test]
  fn test_sort_key_format() {
    let source_key = SourceKey::new(10, 1);
    let position = Position::new(5, 12);

    let sort_key = TextDocumentPositionSortKey::Position {
      source_key,
      line: position.line,
      character: position.character,
      kind: PositionKind::Ident,
    }
    .to_string();

    assert_eq!(sort_key, "10v1|0000000005|0000000012|1");
  }

  #[test]
  fn test_sort_key_ordering() {
    let source_key = SourceKey::new(10, 1);

    let pos1 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 0,
      character: 0,
      kind: PositionKind::Ident,
    }
    .to_string();
    let pos2 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 0,
      character: 10,
      kind: PositionKind::Ident,
    }
    .to_string();
    let pos3 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 1,
      character: 0,
      kind: PositionKind::Ident,
    }
    .to_string();

    assert!(pos1 < pos2);
    assert!(pos2 < pos3);
  }

  #[test]
  fn test_sort_key_same_file_different_positions() {
    let source_key = SourceKey::new(42, 5);

    let pos_line5 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 5,
      character: 0,
      kind: PositionKind::Symbol,
    }
    .to_string();
    let pos_line10 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 10,
      character: 0,
      kind: PositionKind::Symbol,
    }
    .to_string();
    let pos_line15 = TextDocumentPositionSortKey::Position {
      source_key,
      line: 15,
      character: 0,
      kind: PositionKind::Symbol,
    }
    .to_string();

    assert!(pos_line5 < pos_line10);
    assert!(pos_line10 < pos_line15);
  }

  #[test]
  fn test_sort_key_different_files() {
    let source_key_1 = SourceKey::new(10, 1);
    let source_key_2 = SourceKey::new(20, 1);

    let pos1 = TextDocumentPositionSortKey::Position {
      source_key: source_key_1,
      line:       0,
      character:  0,
      kind:       PositionKind::Reference,
    }
    .to_string();
    let pos2 = TextDocumentPositionSortKey::Position {
      source_key: source_key_2,
      line:       0,
      character:  0,
      kind:       PositionKind::Reference,
    }
    .to_string();

    assert_ne!(pos1, pos2);
    assert!(pos1 < pos2);
  }

  #[test]
  fn test_sort_key_different_kinds_same_position() {
    let source_key = SourceKey::new(10, 1);
    let position = Position::new(5, 10);

    let ident = TextDocumentPositionSortKey::Position {
      source_key,
      line: position.line,
      character: position.character,
      kind: PositionKind::Ident,
    }
    .to_string();
    let symbol = TextDocumentPositionSortKey::Position {
      source_key,
      line: position.line,
      character: position.character,
      kind: PositionKind::Symbol,
    }
    .to_string();
    let reference = TextDocumentPositionSortKey::Position {
      source_key,
      line: position.line,
      character: position.character,
      kind: PositionKind::Reference,
    }
    .to_string();

    assert_eq!(ident, "10v1|0000000005|0000000010|1");
    assert_eq!(symbol, "10v1|0000000005|0000000010|2");
    assert_eq!(reference, "10v1|0000000005|0000000010|3");

    assert!(ident < symbol);
    assert!(symbol < reference);
  }
}