Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/ark/src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
//
//

pub(crate) mod ark_file;
pub mod backend;
pub mod capabilities;
pub mod code_action;
pub mod comm;
pub mod completions;
mod config;
pub(crate) mod db;
Comment thread
lionel- marked this conversation as resolved.
mod declarations;
pub mod diagnostics;
pub mod diagnostics_syntax;
Expand Down
144 changes: 144 additions & 0 deletions crates/ark/src/lsp/ark_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//
// ark_file.rs
//
// Copyright (C) 2026 Posit Software, PBC. All rights reserved.
//
//

use aether_lsp_utils::proto::from_proto;
use aether_lsp_utils::proto::to_proto;
use aether_lsp_utils::proto::PositionEncoding;
use oak_db::File;
use tower_lsp::lsp_types;
use url::Url;

use crate::lsp::config::DocumentConfig;
use crate::lsp::db::ArkDb;
use crate::lsp::db::FileArkExt;

/// Editor-managed buffer state, paired with its `oak_db::File`.
///
/// `ArkFile` and `OakDatabase` are sibling fields on `WorldState`, so an
/// `ArkFile` cannot hold a reference to the database. That's why the methods
/// below take `db` as an argument instead of storing a reference, which is the
/// Salsa convention anyway.
#[derive(Debug)]
pub(crate) struct ArkFile {
pub(crate) file: File,
pub(crate) version: Option<i32>,
pub(crate) config: DocumentConfig,
pub(crate) url: Url,
pub(crate) encoding: PositionEncoding,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hoping this encoding field goes away entirely, see a review of a previous pr that i left

@lionel- lionel- Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArkFile in general goes away entirely once we update Ark handlers to Oak layering

}

impl ArkFile {
pub(crate) fn tree_sitter<'db>(&self, db: &'db dyn ArkDb) -> &'db tree_sitter::Tree {
self.file.tree_sitter(db)
}

pub(crate) fn line_index<'db>(&self, db: &'db dyn ArkDb) -> &'db biome_line_index::LineIndex {
self.file.line_index(db)
}

pub(crate) fn contents<'db>(&self, db: &'db dyn ArkDb) -> &'db str {
self.file.contents(db).as_str()
}

pub(crate) fn get_line<'db>(&self, db: &'db dyn ArkDb, line: usize) -> Option<&'db str> {
let line_index = self.line_index(db);
let contents = self.contents(db);

let Some(line_start) = line_index.newlines.get(line) else {
// Forcing a full capture so we can learn the situations in which this occurs
log::error!(
"Requesting line {line} but only {n} lines exist.\n\nContents:\n{contents}\n\nBacktrace:\n{trace}",
n = line_index.len(),
line = line + 1,
trace = std::backtrace::Backtrace::force_capture(),
);
return None;
};

let line_end = line_index
.newlines
.get(line + 1)
.copied()
// if `line` is last, extract text until end of buffer
.unwrap_or_else(|| (contents.len() as u32).into());

let line_start_byte: usize = line_start.to_owned().into();
let line_end_byte: usize = line_end.into();

contents.get(line_start_byte..line_end_byte)
}

pub(crate) fn tree_sitter_point_from_lsp_position(
&self,
db: &dyn ArkDb,
position: lsp_types::Position,
) -> anyhow::Result<tree_sitter::Point> {
let line_col =
from_proto::line_col_from_position(position, self.line_index(db), self.encoding);
Ok(tree_sitter::Point::new(
line_col.line as usize,
line_col.col as usize,
))
}

pub(crate) fn lsp_position_from_tree_sitter_point(
&self,
db: &dyn ArkDb,
point: tree_sitter::Point,
) -> anyhow::Result<lsp_types::Position> {
let line_col = biome_line_index::LineCol {
line: point.row as u32,
col: point.column as u32,
};
to_proto::position_from_line_col(line_col, self.line_index(db), self.encoding)
}

pub(crate) fn lsp_range_from_tree_sitter_range(
&self,
db: &dyn ArkDb,
range: tree_sitter::Range,
) -> anyhow::Result<lsp_types::Range> {
let start = self.lsp_position_from_tree_sitter_point(db, range.start_point)?;
let end = self.lsp_position_from_tree_sitter_point(db, range.end_point)?;
Ok(lsp_types::Range::new(start, end))
}

pub(crate) fn tree_sitter_range_from_lsp_range(
&self,
db: &dyn ArkDb,
range: lsp_types::Range,
) -> anyhow::Result<tree_sitter::Range> {
let start_point = self.tree_sitter_point_from_lsp_position(db, range.start)?;
let end_point = self.tree_sitter_point_from_lsp_position(db, range.end)?;

let text_range = from_proto::text_range(range, self.line_index(db), self.encoding)?;

Ok(tree_sitter::Range {
start_byte: text_range.start().into(),
end_byte: text_range.end().into(),
start_point,
end_point,
})
}
}

#[cfg(test)]
pub(crate) fn test_ark_file(code: &str) -> (oak_db::OakDatabase, ArkFile) {
use aether_path::FilePath;

let db = oak_db::OakDatabase::new();
let url = Url::parse("file:///test.R").unwrap();
let key = FilePath::from_url(&url);
let file = ArkFile {
file: File::new(&db, key, code.to_string(), None),
version: None,
config: DocumentConfig::default(),
url,
encoding: PositionEncoding::Wide(biome_line_index::WideEncoding::Utf16),
};
(db, file)
}
9 changes: 5 additions & 4 deletions crates/ark/src/lsp/code_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use tower_lsp::lsp_types;
use tree_sitter::Range;
use url::Url;

use crate::lsp::ark_file::ArkFile;
use crate::lsp::capabilities::Capabilities;
use crate::lsp::code_action::roxygen::roxygen_documentation;
use crate::lsp::document::Document;
use crate::lsp::db::ArkDb;

mod roxygen;

Expand All @@ -25,14 +26,14 @@ pub(crate) struct CodeActions {
}

pub(crate) fn code_actions(
uri: &Url,
document: &Document,
db: &dyn ArkDb,
file: &ArkFile,
range: Range,
capabilities: &Capabilities,
) -> lsp_types::CodeActionResponse {
let mut actions = CodeActions::new();

roxygen_documentation(&mut actions, uri, document, range, capabilities);
roxygen_documentation(db, file, &mut actions, range, capabilities);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did like having &mut actions up front

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense too, but context params go first by convention.


actions.into_response()
}
Expand Down
Loading
Loading