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
25 changes: 0 additions & 25 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ futures = { version = "0.3.31", features = ["alloc", "executor"], default-featur
lazy_static = "1.5"
logos = "0.15.1"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
ropey = "1.6.1"
# rdf-parsers = "0.1.8"
rdf-parsers = { version = "0.1.15" }
serde = { version = "1.0", features = ["derive"] }
Expand Down
1 change: 0 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ bevy_ecs.workspace = true
chumsky.workspace = true
derive_more.workspace = true
futures.workspace = true
ropey.workspace = true
serde.workspace = true
serde_json.workspace = true
sophia_api.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use futures::lock::Mutex;
use goto_type::GotoTypeRequest;
use references::ReferencesRequest;
use request::{GotoTypeDefinitionParams, GotoTypeDefinitionResponse};
use ropey::Rope;
use crate::text::LineIndex;
use tracing::{debug, error, info, instrument};

use crate::{
Expand Down Expand Up @@ -532,7 +532,7 @@ impl<C: Client> Backend<C> {
(
Source(item.text.clone()),
Label(item.uri.clone()),
RopeC(Rope::from_str(&item.text)),
RopeC(LineIndex::new(&item.text)),
Wrapped(item),
DocumentLinks(Vec::new()),
Open,
Expand Down Expand Up @@ -576,7 +576,7 @@ impl<C: Client> Backend<C> {
};

self.run(move |world| {
let rope_c = RopeC(Rope::from_str(&change.text));
let rope_c = RopeC(LineIndex::new(&change.text));
world
.entity_mut(entity)
.insert((Source(change.text), rope_c));
Expand Down
7 changes: 4 additions & 3 deletions core/src/components/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ pub struct Errors<E>(pub Vec<E>);
#[derive(Component, AsRef, Deref, AsMut, DerefMut, Debug)]
pub struct Source(pub String);

/// [`Component`] containing the current source code as [`ropey::Rope`]
#[derive(Component, AsRef, Deref, AsMut, DerefMut, Debug)]
pub struct RopeC(pub ropey::Rope);
/// [`Component`] containing the current source code as a [`LineIndex`], used for
/// LSP byte ↔ position conversions.
#[derive(Component, AsRef, Deref, AsMut, DerefMut, Debug, Default)]
pub struct RopeC(pub LineIndex);

/// [`Component`] that allows for language specific implementation for certain things, reducing
/// code duplication.
Expand Down
17 changes: 10 additions & 7 deletions core/src/feature/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,24 @@ pub fn prepare_rename(
TripleTarget::Graph => continue,
};

let raw: String = rope.0.slice(span.start..span.end).to_string();
let Some(raw) = rope.0.byte_slice(span.start..span.end) else {
continue;
};
let raw = raw.to_string();
let inner = lang.0.rename_placeholder(&raw);

// Guard: inner must be non-empty
if inner.is_empty() {
continue;
}

// Compute how many chars are stripped from the front.
// `inner` is a sub-slice of `raw`; work entirely in *byte* offsets
// so the math stays consistent with `span` (also bytes) and with
// `range_to_range`. Mixing in char counts here previously produced
// wrong ranges after multi-byte characters.
let prefix_offset = inner.as_ptr() as usize - raw.as_ptr() as usize;
let prefix_chars = raw[..prefix_offset].chars().count();
let inner_char_len = inner.chars().count();

let inner_start = span.start + prefix_chars;
let inner_end = inner_start + inner_char_len;
let inner_start = span.start + prefix_offset;
let inner_end = inner_start + inner.len();

if inner_start >= inner_end || inner_end > span.end {
continue;
Expand Down
66 changes: 47 additions & 19 deletions core/src/feature/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,18 @@ pub fn semantic_tokens_system(
tracing::debug!("semantic_tokens_system called");
for (rope, types, mut req) in &mut query {
let rope = &rope.0;
let mut ts: Vec<Option<SemanticTokenType>> = Vec::with_capacity(rope.len_chars());
// `ts` is indexed by *byte* offset; spans (`r`) are byte ranges.
let mut ts: Vec<Option<SemanticTokenType>> = Vec::with_capacity(rope.len_bytes());
ts.resize(rope.len_bytes(), None);
types.iter().for_each(|Spanned(ty, r)| {
r.clone().for_each(|j| {
if j < ts.len() {
ts[j] = Some(ty.clone())
} else {
tracing::error!(
"Semantic tokens type {} (index={}) falls outside of rope size (chars: {} bytes: {})",
"Semantic tokens type {} (index={}) falls outside of document size ({} bytes)",
ty.as_str(),
j,
rope.len_chars(),
rope.len_bytes()
);
}
Expand Down Expand Up @@ -100,35 +100,63 @@ pub fn semantic_tokens_system(
if let Some(t) = last {
out_tokens.push(TokenHelper {
start,
length: rope.len_chars() - start,
length: rope.len_bytes() - start, // byte length, like the branch above
ty: res.get(&t).cloned().unwrap_or(0),
});
}

let mut pre_line = 0;
let mut pre_start = 0;
// Emit LSP semantic tokens. `start`/`length` on each `TokenHelper` are byte
// offsets/lengths; LSP wants UTF-16 columns and lengths. We walk the source
// once with a monotonically advancing cursor (tokens are sorted, non-
// overlapping), so this is O(n) overall — crucially avoiding an O(line²)
// blow-up on minified single-line documents that a per-token byte→UTF-16
// conversion would incur.
let text = rope.as_str();
let mut cur_byte = 0usize;
let mut cur_line = 0u32;
let mut cur_col = 0u32; // UTF-16 column within the current line
let mut pre_line = 0u32;
let mut pre_col = 0u32;
req.0 = out_tokens
.into_iter()
.flat_map(|token| {
let line = rope.try_byte_to_line(token.start as usize).ok()? as u32;
let first = rope.try_line_to_char(line as usize).ok()? as u32;
let start = rope.try_byte_to_char(token.start as usize).ok()? as u32 - first;
let delta_line = line - pre_line;
.map(|token| {
let advance = |from: usize, to: usize, line: &mut u32, col: &mut u32| -> u32 {
let mut utf16 = 0u32;
for ch in text.get(from..to).unwrap_or("").chars() {
let w = ch.len_utf16() as u32;
utf16 += w;
if ch == '\n' {
*line += 1;
*col = 0;
} else {
*col += w;
}
}
utf16
};

advance(cur_byte, token.start, &mut cur_line, &mut cur_col);
let start_line = cur_line;
let start_col = cur_col;
let length = advance(token.start, token.start + token.length, &mut cur_line, &mut cur_col);
cur_byte = token.start + token.length;

let delta_line = start_line - pre_line;
let delta_start = if delta_line == 0 {
start - pre_start
start_col - pre_col
} else {
start
start_col
};
let ret = Some(SemanticToken {
pre_line = start_line;
pre_col = start_col;

SemanticToken {
delta_line,
delta_start,
length: token.length as u32,
length,
token_type: token.ty as u32,
token_modifiers_bitset: 0,
});
pre_line = line;
pre_start = start;
ret
}
})
.collect();
}
Expand Down
10 changes: 6 additions & 4 deletions core/src/lang.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{borrow::Cow, ops::Range};

use crate::{lsp_types::SemanticTokenType, prelude::TripleTarget, util::offset_to_position};
use crate::{
lsp_types::SemanticTokenType, prelude::TripleTarget, text::LineIndex, util::offset_to_position,
};

pub fn head() -> crate::lsp_types::Range {
let start = crate::lsp_types::Position {
Expand Down Expand Up @@ -89,7 +91,7 @@ pub trait LangHelper: std::fmt::Debug {
fn prefix_edits(
&self,
_source: &str,
_rope: &ropey::Rope,
_rope: &LineIndex,
name: &str,
namespace: &str,
format: crate::components::PrefixFormat,
Expand Down Expand Up @@ -217,7 +219,7 @@ pub trait LangHelper: std::fmt::Debug {
fn inlay_types_hint(
&self,
subject: &Range<usize>,
rope: &ropey::Rope,
rope: &LineIndex,
last_type: Option<&Range<usize>>,
types: Vec<Cow<'_, str>>,
) -> Option<crate::lsp_types::InlayHint> {
Expand All @@ -229,7 +231,7 @@ pub trait LangHelper: std::fmt::Debug {
return None;
}
} else {
let offset = if rope.get_char(subject.start) == Some('[') {
let offset = if rope.char_at_byte(subject.start) == Some('[') {
subject.start + 1
} else {
subject.end
Expand Down
2 changes: 2 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ pub mod backend;
/// Handle platform specific implementations for fetching and spawning tasks.
pub mod client;
pub mod store;
/// Minimal text indexing for LSP position math (replaces ropey).
pub mod text;
/// Common utils
///
/// Includes range transformations between [`std::ops::Range`] and [`swls_core::lsp_types::Range`].
Expand Down
4 changes: 3 additions & 1 deletion core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ pub use crate::{
*,
},
lang::{Lang, LangHelper},
setup_schedule_labels, systems,
setup_schedule_labels,
text::{LineIndex, PositionEncoding},
systems,
systems::prefix::{Prefix, Prefixes},
systems::spawn_or_insert,
util::{
Expand Down
2 changes: 1 addition & 1 deletion core/src/systems/lov/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn init_ontology_extractor(mut commands: Commands, fs: Res<Fs>) {
url.clone(),
(
Source(local.content.to_string()),
RopeC(ropey::Rope::from_str(&local.content)),
RopeC(LineIndex::new(&*local.content)),
Label(url),
Wrapped(item),
Types(HashMap::new()),
Expand Down
2 changes: 1 addition & 1 deletion core/src/systems/lov/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ pub fn spawn_document(
let spawn = spawn_or_insert(
url.clone(),
(
RopeC(ropey::Rope::from_str(&content)),
RopeC(LineIndex::new(&content)),
Source(content.clone()),
Label(url.clone()),
Wrapped(item),
Expand Down
Loading
Loading