From 7009d6026014532dc69a04bb135d1ffc717effd8 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 5 Nov 2025 14:24:03 +0100 Subject: [PATCH] [ty] Refactor `Range` to/from `TextRange` conversion as prep for notebook support (#21230) --- crates/ty_server/src/document/location.rs | 55 ++- crates/ty_server/src/document/range.rs | 359 ++++++++++++------ .../ty_server/src/document/text_document.rs | 4 +- .../ty_server/src/server/api/diagnostics.rs | 9 +- .../src/server/api/requests/completion.rs | 15 +- .../src/server/api/requests/doc_highlights.rs | 11 +- .../server/api/requests/document_symbols.rs | 42 +- .../server/api/requests/goto_declaration.rs | 8 +- .../server/api/requests/goto_definition.rs | 8 +- .../server/api/requests/goto_references.rs | 8 +- .../api/requests/goto_type_definition.rs | 8 +- .../src/server/api/requests/hover.rs | 22 +- .../src/server/api/requests/inlay_hints.rs | 9 +- .../src/server/api/requests/prepare_rename.rs | 9 +- .../src/server/api/requests/rename.rs | 8 +- .../server/api/requests/selection_range.rs | 10 +- .../api/requests/semantic_tokens_range.rs | 11 +- .../src/server/api/requests/signature_help.rs | 8 +- .../server/api/requests/workspace_symbols.rs | 24 +- .../src/server/api/semantic_tokens.rs | 8 +- crates/ty_server/src/server/api/symbols.rs | 25 +- crates/ty_server/src/session.rs | 5 + 22 files changed, 386 insertions(+), 280 deletions(-) diff --git a/crates/ty_server/src/document/location.rs b/crates/ty_server/src/document/location.rs index d5924595b2..f02dc20d98 100644 --- a/crates/ty_server/src/document/location.rs +++ b/crates/ty_server/src/document/location.rs @@ -1,12 +1,9 @@ use crate::PositionEncoding; use crate::document::{FileRangeExt, ToRangeExt}; -use crate::system::file_to_url; use lsp_types::Location; use ruff_db::files::FileRange; -use ruff_db::source::{line_index, source_text}; -use ruff_text_size::Ranged; use ty_ide::{NavigationTarget, ReferenceTarget}; -use ty_project::Db; +use ty_python_semantic::Db; pub(crate) trait ToLink { fn to_location(&self, db: &dyn Db, encoding: PositionEncoding) -> Option; @@ -21,7 +18,9 @@ pub(crate) trait ToLink { impl ToLink for NavigationTarget { fn to_location(&self, db: &dyn Db, encoding: PositionEncoding) -> Option { - FileRange::new(self.file(), self.focus_range()).to_location(db, encoding) + FileRange::new(self.file(), self.focus_range()) + .as_lsp_range(db, encoding) + .to_location() } fn to_link( @@ -31,22 +30,24 @@ impl ToLink for NavigationTarget { encoding: PositionEncoding, ) -> Option { let file = self.file(); - let uri = file_to_url(db, file)?; - let source = source_text(db, file); - let index = line_index(db, file); - let target_range = self.full_range().to_lsp_range(&source, &index, encoding); - let selection_range = self.focus_range().to_lsp_range(&source, &index, encoding); + // Get target_range and URI together to ensure they're consistent (same cell for notebooks) + let target_location = self + .full_range() + .as_lsp_range(db, file, encoding) + .to_location()?; + let target_range = target_location.range; - let src = src.map(|src| { - let source = source_text(db, src.file()); - let index = line_index(db, src.file()); + // For selection_range, we can use as_local_range since we know it's in the same document/cell + let selection_range = self + .focus_range() + .as_lsp_range(db, file, encoding) + .to_local_range(); - src.range().to_lsp_range(&source, &index, encoding) - }); + let src = src.map(|src| src.as_lsp_range(db, encoding).to_local_range()); Some(lsp_types::LocationLink { - target_uri: uri, + target_uri: target_location.uri, target_range, target_selection_range: selection_range, origin_selection_range: src, @@ -56,7 +57,7 @@ impl ToLink for NavigationTarget { impl ToLink for ReferenceTarget { fn to_location(&self, db: &dyn Db, encoding: PositionEncoding) -> Option { - self.file_range().to_location(db, encoding) + self.file_range().as_lsp_range(db, encoding).to_location() } fn to_link( @@ -65,22 +66,18 @@ impl ToLink for ReferenceTarget { src: Option, encoding: PositionEncoding, ) -> Option { - let uri = file_to_url(db, self.file())?; - let source = source_text(db, self.file()); - let index = line_index(db, self.file()); - - let target_range = self.range().to_lsp_range(&source, &index, encoding); + // Get target_range and URI together to ensure they're consistent (same cell for notebooks) + let target_location = self + .range() + .as_lsp_range(db, self.file(), encoding) + .to_location()?; + let target_range = target_location.range; let selection_range = target_range; - let src = src.map(|src| { - let source = source_text(db, src.file()); - let index = line_index(db, src.file()); - - src.range().to_lsp_range(&source, &index, encoding) - }); + let src = src.map(|src| src.as_lsp_range(db, encoding).to_local_range()); Some(lsp_types::LocationLink { - target_uri: uri, + target_uri: target_location.uri, target_range, target_selection_range: selection_range, origin_selection_range: src, diff --git a/crates/ty_server/src/document/range.rs b/crates/ty_server/src/document/range.rs index 1d107e5a30..1e7f381ae5 100644 --- a/crates/ty_server/src/document/range.rs +++ b/crates/ty_server/src/document/range.rs @@ -1,148 +1,288 @@ use super::PositionEncoding; -use super::notebook; use crate::system::file_to_url; +use ty_python_semantic::Db; use lsp_types as types; -use lsp_types::Location; - -use ruff_db::files::FileRange; +use lsp_types::{Location, Position, Url}; +use ruff_db::files::{File, FileRange}; use ruff_db::source::{line_index, source_text}; -use ruff_notebook::NotebookIndex; use ruff_source_file::LineIndex; use ruff_source_file::{OneIndexed, SourceLocation}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use ty_python_semantic::Db; -#[expect(dead_code)] -pub(crate) struct NotebookRange { - pub(crate) cell: notebook::CellId, - pub(crate) range: types::Range, +/// Represents a range that has been prepared for LSP conversion but requires +/// a decision about how to use it - either as a local range within the same +/// document/cell, or as a location that can reference any document in the project. +#[derive(Clone)] +pub(crate) struct LspRange<'db> { + file: File, + range: TextRange, + db: &'db dyn Db, + encoding: PositionEncoding, +} + +impl std::fmt::Debug for LspRange<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LspRange") + .field("range", &self.range) + .field("file", &self.file) + .field("encoding", &self.encoding) + .finish_non_exhaustive() + } +} + +impl LspRange<'_> { + /// Convert to an LSP Range for use within the same document/cell. + /// Returns only the LSP Range without any URI information. + /// + /// Use this when you already have a URI context and this range is guaranteed + /// to be within the same document/cell: + /// - Selection ranges within a `LocationLink` (where `target_uri` provides context) + /// - Additional ranges in the same cell (e.g., `selection_range` when you already have `target_range`) + /// + /// Do NOT use this for standalone ranges - use `to_location()` instead to ensure + /// the URI and range are consistent. + pub(crate) fn to_local_range(&self) -> types::Range { + self.to_uri_and_range().1 + } + + /// Convert to a Location that can reference any document. + /// Returns a Location with both URI and Range. + /// + /// Use this for: + /// - Go-to-definition targets + /// - References + /// - Diagnostics related information + /// - Any cross-file navigation + pub(crate) fn to_location(&self) -> Option { + let (uri, range) = self.to_uri_and_range(); + Some(Location { uri: uri?, range }) + } + + pub(crate) fn to_uri_and_range(&self) -> (Option, lsp_types::Range) { + let source = source_text(self.db, self.file); + let index = line_index(self.db, self.file); + + let uri = file_to_url(self.db, self.file); + let range = text_range_to_lsp_range(self.range, &source, &index, self.encoding); + (uri, range) + } +} + +/// Represents a position that has been prepared for LSP conversion but requires +/// a decision about how to use it - either as a local position within the same +/// document/cell, or as a location with a single-point range that can reference +/// any document in the project. +#[derive(Clone)] +pub(crate) struct LspPosition<'db> { + file: File, + position: TextSize, + db: &'db dyn Db, + encoding: PositionEncoding, +} + +impl std::fmt::Debug for LspPosition<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LspPosition") + .field("position", &self.position) + .field("file", &self.file) + .field("encoding", &self.encoding) + .finish_non_exhaustive() + } +} + +impl LspPosition<'_> { + /// Convert to an LSP Position for use within the same document/cell. + /// Returns only the LSP Position without any URI information. + /// + /// Use this when you already have a URI context and this position is guaranteed + /// to be within the same document/cell: + /// - Inlay hints (where the document URI is already known) + /// - Positions within the same cell as a parent range + /// + /// Do NOT use this for standalone positions that might need a URI - use + /// `to_location()` instead to ensure the URI and position are consistent. + pub(crate) fn to_local_position(&self) -> types::Position { + self.to_location().1 + } + + /// Convert to a Location with a single-point range that can reference any document. + /// Returns a Location with both URI and a range where start == end. + /// + /// Use this for any cross-file navigation where you need both URI and position. + pub(crate) fn to_location(&self) -> (Option, Position) { + let source = source_text(self.db, self.file); + let index = line_index(self.db, self.file); + + let uri = file_to_url(self.db, self.file); + let position = text_size_to_lsp_position(self.position, &source, &index, self.encoding); + (uri, position) + } } pub(crate) trait RangeExt { - fn to_text_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) - -> TextRange; + /// Convert an LSP Range to internal `TextRange`. + fn to_text_range( + &self, + db: &dyn Db, + file: File, + url: &lsp_types::Url, + encoding: PositionEncoding, + ) -> TextRange; +} + +impl RangeExt for lsp_types::Range { + fn to_text_range( + &self, + db: &dyn Db, + file: File, + url: &lsp_types::Url, + encoding: PositionEncoding, + ) -> TextRange { + let start = self.start.to_text_size(db, file, url, encoding); + let end = self.end.to_text_size(db, file, url, encoding); + + TextRange::new(start, end) + } } pub(crate) trait PositionExt { - fn to_text_size(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> TextSize; + /// Convert an LSP Position to internal `TextSize`. + fn to_text_size( + &self, + db: &dyn Db, + file: File, + url: &lsp_types::Url, + encoding: PositionEncoding, + ) -> TextSize; +} + +impl PositionExt for lsp_types::Position { + fn to_text_size( + &self, + db: &dyn Db, + file: File, + _url: &lsp_types::Url, + encoding: PositionEncoding, + ) -> TextSize { + let source = source_text(db, file); + let index = line_index(db, file); + + lsp_position_to_text_size(*self, &source, &index, encoding) + } } pub(crate) trait TextSizeExt { - fn to_position( - self, - text: &str, - index: &LineIndex, + /// Converts this position to an `LspPosition`, which then requires an explicit + /// decision about how to use it (as a local position or as a location). + fn as_lsp_position<'db>( + &self, + db: &'db dyn Db, + file: File, encoding: PositionEncoding, - ) -> types::Position + ) -> LspPosition<'db> where Self: Sized; } impl TextSizeExt for TextSize { - fn to_position( - self, - text: &str, - index: &LineIndex, + fn as_lsp_position<'db>( + &self, + db: &'db dyn Db, + file: File, encoding: PositionEncoding, - ) -> types::Position { - let source_location = index.source_location(self, text, encoding.into()); - source_location_to_position(&source_location) + ) -> LspPosition<'db> { + LspPosition { + file, + position: *self, + db, + encoding, + } } } pub(crate) trait ToRangeExt { - fn to_lsp_range( + /// Converts this range to an `LspRange`, which then requires an explicit + /// decision about how to use it (as a local range or as a location). + fn as_lsp_range<'db>( &self, - text: &str, - index: &LineIndex, + db: &'db dyn Db, + file: File, encoding: PositionEncoding, - ) -> types::Range; - - #[expect(dead_code)] - fn to_notebook_range( - &self, - text: &str, - source_index: &LineIndex, - notebook_index: &NotebookIndex, - encoding: PositionEncoding, - ) -> NotebookRange; + ) -> LspRange<'db>; } fn u32_index_to_usize(index: u32) -> usize { usize::try_from(index).expect("u32 fits in usize") } -impl PositionExt for lsp_types::Position { - fn to_text_size(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> TextSize { - index.offset( - SourceLocation { - line: OneIndexed::from_zero_indexed(u32_index_to_usize(self.line)), - character_offset: OneIndexed::from_zero_indexed(u32_index_to_usize(self.character)), - }, - text, - encoding.into(), - ) +fn text_size_to_lsp_position( + offset: TextSize, + text: &str, + index: &LineIndex, + encoding: PositionEncoding, +) -> types::Position { + let source_location = index.source_location(offset, text, encoding.into()); + source_location_to_position(&source_location) +} + +fn text_range_to_lsp_range( + range: TextRange, + text: &str, + index: &LineIndex, + encoding: PositionEncoding, +) -> types::Range { + types::Range { + start: text_size_to_lsp_position(range.start(), text, index, encoding), + end: text_size_to_lsp_position(range.end(), text, index, encoding), } } -impl RangeExt for lsp_types::Range { - fn to_text_range( - &self, - text: &str, - index: &LineIndex, - encoding: PositionEncoding, - ) -> TextRange { - TextRange::new( - self.start.to_text_size(text, index, encoding), - self.end.to_text_size(text, index, encoding), - ) - } +/// Helper function to convert an LSP Position to internal `TextSize`. +/// This is used internally by the `PositionExt` trait and other helpers. +fn lsp_position_to_text_size( + position: lsp_types::Position, + text: &str, + index: &LineIndex, + encoding: PositionEncoding, +) -> TextSize { + index.offset( + SourceLocation { + line: OneIndexed::from_zero_indexed(u32_index_to_usize(position.line)), + character_offset: OneIndexed::from_zero_indexed(u32_index_to_usize(position.character)), + }, + text, + encoding.into(), + ) +} + +/// Helper function to convert an LSP Range to internal `TextRange`. +/// This is used internally by the `RangeExt` trait and in special cases +/// where `db` and `file` are not available (e.g., when applying document changes). +pub(crate) fn lsp_range_to_text_range( + range: lsp_types::Range, + text: &str, + index: &LineIndex, + encoding: PositionEncoding, +) -> TextRange { + TextRange::new( + lsp_position_to_text_size(range.start, text, index, encoding), + lsp_position_to_text_size(range.end, text, index, encoding), + ) } impl ToRangeExt for TextRange { - fn to_lsp_range( + fn as_lsp_range<'db>( &self, - text: &str, - index: &LineIndex, + db: &'db dyn Db, + file: File, encoding: PositionEncoding, - ) -> types::Range { - types::Range { - start: self.start().to_position(text, index, encoding), - end: self.end().to_position(text, index, encoding), - } - } - - fn to_notebook_range( - &self, - text: &str, - source_index: &LineIndex, - notebook_index: &NotebookIndex, - encoding: PositionEncoding, - ) -> NotebookRange { - let start = source_index.source_location(self.start(), text, encoding.into()); - let mut end = source_index.source_location(self.end(), text, encoding.into()); - let starting_cell = notebook_index.cell(start.line); - - // weird edge case here - if the end of the range is where the newline after the cell got added (making it 'out of bounds') - // we need to move it one character back (which should place it at the end of the last line). - // we test this by checking if the ending offset is in a different (or nonexistent) cell compared to the cell of the starting offset. - if notebook_index.cell(end.line) != starting_cell { - end.line = end.line.saturating_sub(1); - let offset = self.end().checked_sub(1.into()).unwrap_or_default(); - end.character_offset = source_index - .source_location(offset, text, encoding.into()) - .character_offset; - } - - let start = source_location_to_position(¬ebook_index.translate_source_location(&start)); - let end = source_location_to_position(¬ebook_index.translate_source_location(&end)); - - NotebookRange { - cell: starting_cell - .map(OneIndexed::to_zero_indexed) - .unwrap_or_default(), - range: types::Range { start, end }, + ) -> LspRange<'db> { + LspRange { + file, + range: *self, + db, + encoding, } } } @@ -156,17 +296,18 @@ fn source_location_to_position(location: &SourceLocation) -> types::Position { } pub(crate) trait FileRangeExt { - fn to_location(&self, db: &dyn Db, encoding: PositionEncoding) -> Option; + /// Converts this file range to an `LspRange`, which then requires an explicit + /// decision about how to use it (as a local range or as a location). + fn as_lsp_range<'db>(&self, db: &'db dyn Db, encoding: PositionEncoding) -> LspRange<'db>; } impl FileRangeExt for FileRange { - fn to_location(&self, db: &dyn Db, encoding: PositionEncoding) -> Option { - let file = self.file(); - let uri = file_to_url(db, file)?; - let source = source_text(db, file); - let line_index = line_index(db, file); - - let range = self.range().to_lsp_range(&source, &line_index, encoding); - Some(Location { uri, range }) + fn as_lsp_range<'db>(&self, db: &'db dyn Db, encoding: PositionEncoding) -> LspRange<'db> { + LspRange { + file: self.file(), + range: self.range(), + db, + encoding, + } } } diff --git a/crates/ty_server/src/document/text_document.rs b/crates/ty_server/src/document/text_document.rs index 9898dd670b..e6cd4c4e0b 100644 --- a/crates/ty_server/src/document/text_document.rs +++ b/crates/ty_server/src/document/text_document.rs @@ -3,7 +3,7 @@ use ruff_source_file::LineIndex; use crate::PositionEncoding; -use super::RangeExt; +use super::range::lsp_range_to_text_range; pub(crate) type DocumentVersion = i32; @@ -114,7 +114,7 @@ impl TextDocument { } in changes { if let Some(range) = range { - let range = range.to_text_range(&new_contents, &active_index, encoding); + let range = lsp_range_to_text_range(range, &new_contents, &active_index, encoding); new_contents.replace_range( usize::from(range.start())..usize::from(range.end()), diff --git a/crates/ty_server/src/server/api/diagnostics.rs b/crates/ty_server/src/server/api/diagnostics.rs index 7680dc1bad..adbb17dcdf 100644 --- a/crates/ty_server/src/server/api/diagnostics.rs +++ b/crates/ty_server/src/server/api/diagnostics.rs @@ -9,7 +9,6 @@ use rustc_hash::FxHashMap; use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic}; use ruff_db::files::FileRange; -use ruff_db::source::{line_index, source_text}; use ruff_db::system::SystemPathBuf; use ty_project::{Db, ProjectDatabase}; @@ -279,11 +278,9 @@ pub(super) fn to_lsp_diagnostic( ) -> Diagnostic { let range = if let Some(span) = diagnostic.primary_span() { let file = span.expect_ty_file(); - let index = line_index(db, file); - let source = source_text(db, file); span.range() - .map(|range| range.to_lsp_range(&source, &index, encoding)) + .map(|range| range.as_lsp_range(db, file, encoding).to_local_range()) .unwrap_or_default() } else { Range::default() @@ -365,7 +362,7 @@ fn annotation_to_related_information( let annotation_message = annotation.get_message()?; let range = FileRange::try_from(span).ok()?; - let location = range.to_location(db, encoding)?; + let location = range.as_lsp_range(db, encoding).to_location()?; Some(DiagnosticRelatedInformation { location, @@ -383,7 +380,7 @@ fn sub_diagnostic_to_related_information( let span = primary_annotation.get_span(); let range = FileRange::try_from(span).ok()?; - let location = range.to_location(db, encoding)?; + let location = range.as_lsp_range(db, encoding).to_location()?; Some(DiagnosticRelatedInformation { location, diff --git a/crates/ty_server/src/server/api/requests/completion.rs b/crates/ty_server/src/server/api/requests/completion.rs index bf712c5efb..6473661939 100644 --- a/crates/ty_server/src/server/api/requests/completion.rs +++ b/crates/ty_server/src/server/api/requests/completion.rs @@ -6,7 +6,6 @@ use lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, CompletionParams, CompletionResponse, Documentation, TextEdit, Url, }; -use ruff_db::source::{line_index, source_text}; use ruff_source_file::OneIndexed; use ruff_text_size::Ranged; use ty_ide::{CompletionKind, CompletionSettings, completion}; @@ -49,11 +48,10 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); let settings = CompletionSettings { @@ -73,9 +71,10 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler { let kind = comp.kind(db).map(ty_kind_to_lsp_kind); let type_display = comp.ty.map(|ty| ty.display(db).to_string()); let import_edit = comp.import.as_ref().map(|edit| { - let range = - edit.range() - .to_lsp_range(&source, &line_index, snapshot.encoding()); + let range = edit + .range() + .as_lsp_range(db, file, snapshot.encoding()) + .to_local_range(); TextEdit { range, new_text: edit.content().map(ToString::to_string).unwrap_or_default(), diff --git a/crates/ty_server/src/server/api/requests/doc_highlights.rs b/crates/ty_server/src/server/api/requests/doc_highlights.rs index b5b6d0d9ab..c96c3d4fef 100644 --- a/crates/ty_server/src/server/api/requests/doc_highlights.rs +++ b/crates/ty_server/src/server/api/requests/doc_highlights.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::DocumentHighlightRequest; use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::{ReferenceKind, document_highlights}; use ty_project::ProjectDatabase; @@ -41,11 +40,10 @@ impl BackgroundDocumentRequestHandler for DocumentHighlightRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); @@ -58,7 +56,8 @@ impl BackgroundDocumentRequestHandler for DocumentHighlightRequestHandler { .map(|target| { let range = target .range() - .to_lsp_range(&source, &line_index, snapshot.encoding()); + .as_lsp_range(db, file, snapshot.encoding()) + .to_local_range(); let kind = match target.kind() { ReferenceKind::Read => Some(DocumentHighlightKind::READ), diff --git a/crates/ty_server/src/server/api/requests/document_symbols.rs b/crates/ty_server/src/server/api/requests/document_symbols.rs index ea5ee312c6..980e0850ef 100644 --- a/crates/ty_server/src/server/api/requests/document_symbols.rs +++ b/crates/ty_server/src/server/api/requests/document_symbols.rs @@ -2,10 +2,9 @@ use std::borrow::Cow; use lsp_types::request::DocumentSymbolRequest; use lsp_types::{DocumentSymbol, DocumentSymbolParams, SymbolInformation, Url}; -use ruff_db::source::{line_index, source_text}; -use ruff_source_file::LineIndex; +use ruff_db::files::File; use ty_ide::{HierarchicalSymbols, SymbolId, SymbolInfo, document_symbols}; -use ty_project::ProjectDatabase; +use ty_project::{Db, ProjectDatabase}; use crate::document::{PositionEncoding, ToRangeExt}; use crate::server::api::symbols::{convert_symbol_kind, convert_to_lsp_symbol_information}; @@ -30,7 +29,7 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler { db: &ProjectDatabase, snapshot: &DocumentSnapshot, _client: &Client, - params: DocumentSymbolParams, + _params: DocumentSymbolParams, ) -> crate::server::Result> { if snapshot .workspace_settings() @@ -43,9 +42,6 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); - // Check if the client supports hierarchical document symbols let supports_hierarchical = snapshot .resolved_client_capabilities() @@ -62,11 +58,11 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler { .iter() .map(|(id, symbol)| { convert_to_lsp_document_symbol( + db, + file, &symbols, id, symbol, - &source, - &line_index, snapshot.encoding(), ) }) @@ -77,14 +73,8 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler { // Return flattened symbols as SymbolInformation let lsp_symbols: Vec = symbols .iter() - .map(|(_, symbol)| { - convert_to_lsp_symbol_information( - symbol, - ¶ms.text_document.uri, - &source, - &line_index, - snapshot.encoding(), - ) + .filter_map(|(_, symbol)| { + convert_to_lsp_symbol_information(db, file, symbol, snapshot.encoding()) }) .collect(); @@ -96,11 +86,11 @@ impl BackgroundDocumentRequestHandler for DocumentSymbolRequestHandler { impl RetriableRequestHandler for DocumentSymbolRequestHandler {} fn convert_to_lsp_document_symbol( + db: &dyn Db, + file: File, symbols: &HierarchicalSymbols, id: SymbolId, symbol: SymbolInfo<'_>, - source: &str, - line_index: &LineIndex, encoding: PositionEncoding, ) -> DocumentSymbol { let symbol_kind = convert_symbol_kind(symbol.kind); @@ -112,15 +102,19 @@ fn convert_to_lsp_document_symbol( tags: None, #[allow(deprecated)] deprecated: None, - range: symbol.full_range.to_lsp_range(source, line_index, encoding), - selection_range: symbol.name_range.to_lsp_range(source, line_index, encoding), + range: symbol + .full_range + .as_lsp_range(db, file, encoding) + .to_local_range(), + selection_range: symbol + .name_range + .as_lsp_range(db, file, encoding) + .to_local_range(), children: Some( symbols .children(id) .map(|(child_id, child)| { - convert_to_lsp_document_symbol( - symbols, child_id, child, source, line_index, encoding, - ) + convert_to_lsp_document_symbol(db, file, symbols, child_id, child, encoding) }) .collect(), ), diff --git a/crates/ty_server/src/server/api/requests/goto_declaration.rs b/crates/ty_server/src/server/api/requests/goto_declaration.rs index 1c16a74bc5..2a8c931401 100644 --- a/crates/ty_server/src/server/api/requests/goto_declaration.rs +++ b/crates/ty_server/src/server/api/requests/goto_declaration.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::{GotoDeclaration, GotoDeclarationParams}; use lsp_types::{GotoDefinitionResponse, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::goto_declaration; use ty_project::ProjectDatabase; @@ -41,11 +40,10 @@ impl BackgroundDocumentRequestHandler for GotoDeclarationRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/goto_definition.rs b/crates/ty_server/src/server/api/requests/goto_definition.rs index bc33411778..343f90a5c9 100644 --- a/crates/ty_server/src/server/api/requests/goto_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_definition.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::GotoDefinition; use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::goto_definition; use ty_project::ProjectDatabase; @@ -41,11 +40,10 @@ impl BackgroundDocumentRequestHandler for GotoDefinitionRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/goto_references.rs b/crates/ty_server/src/server/api/requests/goto_references.rs index 3afaf28b14..6cdb8e21a4 100644 --- a/crates/ty_server/src/server/api/requests/goto_references.rs +++ b/crates/ty_server/src/server/api/requests/goto_references.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::References; use lsp_types::{Location, ReferenceParams, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::goto_references; use ty_project::ProjectDatabase; @@ -41,11 +40,10 @@ impl BackgroundDocumentRequestHandler for ReferencesRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/goto_type_definition.rs b/crates/ty_server/src/server/api/requests/goto_type_definition.rs index 379defa344..11564f50d7 100644 --- a/crates/ty_server/src/server/api/requests/goto_type_definition.rs +++ b/crates/ty_server/src/server/api/requests/goto_type_definition.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::{GotoTypeDefinition, GotoTypeDefinitionParams}; use lsp_types::{GotoDefinitionResponse, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::goto_type_definition; use ty_project::ProjectDatabase; @@ -41,11 +40,10 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/hover.rs b/crates/ty_server/src/server/api/requests/hover.rs index cc8f8e0dab..d051007003 100644 --- a/crates/ty_server/src/server/api/requests/hover.rs +++ b/crates/ty_server/src/server/api/requests/hover.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::document::{PositionExt, ToRangeExt}; +use crate::document::{FileRangeExt, PositionExt}; use crate::server::api::traits::{ BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler, }; @@ -8,8 +8,6 @@ use crate::session::DocumentSnapshot; use crate::session::client::Client; use lsp_types::request::HoverRequest; use lsp_types::{HoverContents, HoverParams, MarkupContent, Url}; -use ruff_db::source::{line_index, source_text}; -use ruff_text_size::Ranged; use ty_ide::{MarkupKind, hover}; use ty_project::ProjectDatabase; @@ -41,11 +39,10 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); @@ -69,11 +66,12 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler { kind: lsp_markup_kind, value: contents, }), - range: Some(range_info.file_range().range().to_lsp_range( - &source, - &line_index, - snapshot.encoding(), - )), + range: Some( + range_info + .file_range() + .as_lsp_range(db, snapshot.encoding()) + .to_local_range(), + ), })) } } diff --git a/crates/ty_server/src/server/api/requests/inlay_hints.rs b/crates/ty_server/src/server/api/requests/inlay_hints.rs index 21eb1d09b6..ec445f9b1e 100644 --- a/crates/ty_server/src/server/api/requests/inlay_hints.rs +++ b/crates/ty_server/src/server/api/requests/inlay_hints.rs @@ -8,7 +8,6 @@ use crate::session::DocumentSnapshot; use crate::session::client::Client; use lsp_types::request::InlayHintRequest; use lsp_types::{InlayHintParams, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::{InlayHintKind, InlayHintLabel, inlay_hints}; use ty_project::ProjectDatabase; @@ -40,12 +39,9 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { return Ok(None); }; - let index = line_index(db, file); - let source = source_text(db, file); - let range = params .range - .to_text_range(&source, &index, snapshot.encoding()); + .to_text_range(db, file, snapshot.url(), snapshot.encoding()); let inlay_hints = inlay_hints(db, file, range, workspace_settings.inlay_hints()); @@ -54,7 +50,8 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { .map(|hint| lsp_types::InlayHint { position: hint .position - .to_position(&source, &index, snapshot.encoding()), + .as_lsp_position(db, file, snapshot.encoding()) + .to_local_position(), label: inlay_hint_label(&hint.label), kind: Some(inlay_hint_kind(&hint.kind)), tooltip: None, diff --git a/crates/ty_server/src/server/api/requests/prepare_rename.rs b/crates/ty_server/src/server/api/requests/prepare_rename.rs index a12541729d..2593122530 100644 --- a/crates/ty_server/src/server/api/requests/prepare_rename.rs +++ b/crates/ty_server/src/server/api/requests/prepare_rename.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::PrepareRenameRequest; use lsp_types::{PrepareRenameResponse, TextDocumentPositionParams, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::can_rename; use ty_project::ProjectDatabase; @@ -41,17 +40,17 @@ impl BackgroundDocumentRequestHandler for PrepareRenameRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params .position - .to_text_size(&source, &line_index, snapshot.encoding()); + .to_text_size(db, file, snapshot.url(), snapshot.encoding()); let Some(range) = can_rename(db, file, offset) else { return Ok(None); }; - let lsp_range = range.to_lsp_range(&source, &line_index, snapshot.encoding()); + let lsp_range = range + .as_lsp_range(db, file, snapshot.encoding()) + .to_local_range(); Ok(Some(PrepareRenameResponse::Range(lsp_range))) } diff --git a/crates/ty_server/src/server/api/requests/rename.rs b/crates/ty_server/src/server/api/requests/rename.rs index d434cb733e..efa3891ced 100644 --- a/crates/ty_server/src/server/api/requests/rename.rs +++ b/crates/ty_server/src/server/api/requests/rename.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use lsp_types::request::Rename; use lsp_types::{RenameParams, TextEdit, Url, WorkspaceEdit}; -use ruff_db::source::{line_index, source_text}; use ty_ide::rename; use ty_project::ProjectDatabase; @@ -42,11 +41,10 @@ impl BackgroundDocumentRequestHandler for RenameRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/selection_range.rs b/crates/ty_server/src/server/api/requests/selection_range.rs index 516ea6aeda..77d9df4c25 100644 --- a/crates/ty_server/src/server/api/requests/selection_range.rs +++ b/crates/ty_server/src/server/api/requests/selection_range.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use lsp_types::request::SelectionRangeRequest; use lsp_types::{SelectionRange as LspSelectionRange, SelectionRangeParams, Url}; -use ruff_db::source::{line_index, source_text}; use ty_ide::selection_range; use ty_project::ProjectDatabase; @@ -41,13 +40,10 @@ impl BackgroundDocumentRequestHandler for SelectionRangeRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); - let mut results = Vec::new(); for position in params.positions { - let offset = position.to_text_size(&source, &line_index, snapshot.encoding()); + let offset = position.to_text_size(db, file, snapshot.url(), snapshot.encoding()); let ranges = selection_range(db, file, offset); if !ranges.is_empty() { @@ -55,7 +51,9 @@ impl BackgroundDocumentRequestHandler for SelectionRangeRequestHandler { let mut lsp_range = None; for &range in &ranges { lsp_range = Some(LspSelectionRange { - range: range.to_lsp_range(&source, &line_index, snapshot.encoding()), + range: range + .as_lsp_range(db, file, snapshot.encoding()) + .to_local_range(), parent: lsp_range.map(Box::new), }); } diff --git a/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs b/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs index 03193b32a6..7daa116876 100644 --- a/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs +++ b/crates/ty_server/src/server/api/requests/semantic_tokens_range.rs @@ -8,7 +8,6 @@ use crate::server::api::traits::{ use crate::session::DocumentSnapshot; use crate::session::client::Client; use lsp_types::{SemanticTokens, SemanticTokensRangeParams, SemanticTokensRangeResult, Url}; -use ruff_db::source::{line_index, source_text}; use ty_project::ProjectDatabase; pub(crate) struct SemanticTokensRangeRequestHandler; @@ -39,13 +38,11 @@ impl BackgroundDocumentRequestHandler for SemanticTokensRangeRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); - // Convert LSP range to text offsets - let requested_range = params - .range - .to_text_range(&source, &line_index, snapshot.encoding()); + let requested_range = + params + .range + .to_text_range(db, file, snapshot.url(), snapshot.encoding()); let lsp_tokens = generate_semantic_tokens( db, diff --git a/crates/ty_server/src/server/api/requests/signature_help.rs b/crates/ty_server/src/server/api/requests/signature_help.rs index f9b20cccd9..99d60c398f 100644 --- a/crates/ty_server/src/server/api/requests/signature_help.rs +++ b/crates/ty_server/src/server/api/requests/signature_help.rs @@ -11,7 +11,6 @@ use lsp_types::{ Documentation, ParameterInformation, ParameterLabel, SignatureHelp, SignatureHelpParams, SignatureInformation, Url, }; -use ruff_db::source::{line_index, source_text}; use ty_ide::signature_help; use ty_project::ProjectDatabase; @@ -43,11 +42,10 @@ impl BackgroundDocumentRequestHandler for SignatureHelpRequestHandler { return Ok(None); }; - let source = source_text(db, file); - let line_index = line_index(db, file); let offset = params.text_document_position_params.position.to_text_size( - &source, - &line_index, + db, + file, + snapshot.url(), snapshot.encoding(), ); diff --git a/crates/ty_server/src/server/api/requests/workspace_symbols.rs b/crates/ty_server/src/server/api/requests/workspace_symbols.rs index a964954546..252857e7e4 100644 --- a/crates/ty_server/src/server/api/requests/workspace_symbols.rs +++ b/crates/ty_server/src/server/api/requests/workspace_symbols.rs @@ -8,8 +8,6 @@ use crate::server::api::traits::{ }; use crate::session::SessionSnapshot; use crate::session::client::Client; -use crate::system::file_to_url; -use ruff_db::source::{line_index, source_text}; pub(crate) struct WorkspaceSymbolRequestHandler; @@ -41,23 +39,19 @@ impl BackgroundRequestHandler for WorkspaceSymbolRequestHandler { for workspace_symbol_info in workspace_symbol_infos { let WorkspaceSymbolInfo { symbol, file } = workspace_symbol_info; - // Get file information for URL conversion - let source = source_text(db, file); - let line_index = line_index(db, file); - - // Convert file to URL - let Some(url) = file_to_url(db, file) else { - tracing::debug!("Failed to convert file to URL at {}", file.path(db)); - continue; - }; - // Get position encoding from session let encoding = snapshot.position_encoding(); - let lsp_symbol = - convert_to_lsp_symbol_information(symbol, &url, &source, &line_index, encoding); + let Some(symbol) = convert_to_lsp_symbol_information(db, file, symbol, encoding) + else { + tracing::debug!( + "Failed to convert symbol '{}' to LSP symbol information", + file.path(db) + ); + continue; + }; - all_symbols.push(lsp_symbol); + all_symbols.push(symbol); } } diff --git a/crates/ty_server/src/server/api/semantic_tokens.rs b/crates/ty_server/src/server/api/semantic_tokens.rs index b168ef7877..ee9808b791 100644 --- a/crates/ty_server/src/server/api/semantic_tokens.rs +++ b/crates/ty_server/src/server/api/semantic_tokens.rs @@ -1,5 +1,5 @@ use lsp_types::SemanticToken; -use ruff_db::source::{line_index, source_text}; +use ruff_db::source::source_text; use ruff_text_size::{Ranged, TextRange}; use ty_ide::semantic_tokens; use ty_project::ProjectDatabase; @@ -16,7 +16,6 @@ pub(crate) fn generate_semantic_tokens( multiline_token_support: bool, ) -> Vec { let source = source_text(db, file); - let line_index = line_index(db, file); let semantic_token_data = semantic_tokens(db, file, range); // Convert semantic tokens to LSP format @@ -25,7 +24,10 @@ pub(crate) fn generate_semantic_tokens( let mut prev_start = 0u32; for token in &*semantic_token_data { - let lsp_range = token.range().to_lsp_range(&source, &line_index, encoding); + let lsp_range = token + .range() + .as_lsp_range(db, file, encoding) + .to_local_range(); let line = lsp_range.start.line; let character = lsp_range.start.character; diff --git a/crates/ty_server/src/server/api/symbols.rs b/crates/ty_server/src/server/api/symbols.rs index 396f236e8d..fc6c1bc18c 100644 --- a/crates/ty_server/src/server/api/symbols.rs +++ b/crates/ty_server/src/server/api/symbols.rs @@ -1,9 +1,9 @@ //! Utility functions common to language server request handlers //! that return symbol information. -use lsp_types::{SymbolInformation, SymbolKind, Url}; -use ruff_source_file::LineIndex; +use lsp_types::{SymbolInformation, SymbolKind}; use ty_ide::SymbolInfo; +use ty_project::Db; use crate::document::{PositionEncoding, ToRangeExt}; @@ -27,24 +27,25 @@ pub(crate) fn convert_symbol_kind(kind: ty_ide::SymbolKind) -> SymbolKind { /// Convert a `ty_ide` `SymbolInfo` to LSP `SymbolInformation` pub(crate) fn convert_to_lsp_symbol_information( + db: &dyn Db, + file: ruff_db::files::File, symbol: SymbolInfo<'_>, - uri: &Url, - source: &str, - line_index: &LineIndex, encoding: PositionEncoding, -) -> SymbolInformation { +) -> Option { let symbol_kind = convert_symbol_kind(symbol.kind); - SymbolInformation { + let location = symbol + .full_range + .as_lsp_range(db, file, encoding) + .to_location()?; + + Some(SymbolInformation { name: symbol.name.into_owned(), kind: symbol_kind, tags: None, #[allow(deprecated)] deprecated: None, - location: lsp_types::Location { - uri: uri.clone(), - range: symbol.full_range.to_lsp_range(source, line_index, encoding), - }, + location, container_name: None, - } + }) } diff --git a/crates/ty_server/src/session.rs b/crates/ty_server/src/session.rs index c5daec77e3..9cc3553342 100644 --- a/crates/ty_server/src/session.rs +++ b/crates/ty_server/src/session.rs @@ -1028,6 +1028,11 @@ impl DocumentSnapshot { &self.document } + /// Returns the URL of the document. + pub(crate) fn url(&self) -> &lsp_types::Url { + self.document.url() + } + pub(crate) fn notebook(&self) -> Option<&NotebookDocument> { self.notebook.as_deref() }