[red-knot] Fix offset handling in playground for 2-code-point UTF16 characters (#17520)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[Knot Playground] Release / publish (push) Waiting to run

This commit is contained in:
Micha Reiser 2025-04-27 11:44:55 +01:00 committed by GitHub
parent 1c65e0ad25
commit 1bdb22c139
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 41 deletions

View file

@ -19,7 +19,7 @@ use ruff_db::system::{
use ruff_db::Upcast;
use ruff_notebook::Notebook;
use ruff_python_formatter::formatted_file;
use ruff_source_file::{LineColumn, LineIndex, OneIndexed, PositionEncoding, SourceLocation};
use ruff_source_file::{LineIndex, OneIndexed, SourceLocation};
use ruff_text_size::{Ranged, TextSize};
use wasm_bindgen::prelude::*;
@ -42,13 +42,18 @@ pub fn run() {
#[wasm_bindgen]
pub struct Workspace {
db: ProjectDatabase,
position_encoding: PositionEncoding,
system: WasmSystem,
}
#[wasm_bindgen]
impl Workspace {
#[wasm_bindgen(constructor)]
pub fn new(root: &str, options: JsValue) -> Result<Workspace, Error> {
pub fn new(
root: &str,
position_encoding: PositionEncoding,
options: JsValue,
) -> Result<Workspace, Error> {
let options = Options::deserialize_with(
ValueSource::Cli,
serde_wasm_bindgen::Deserializer::from(options),
@ -62,7 +67,11 @@ impl Workspace {
let db = ProjectDatabase::new(project, system.clone()).map_err(into_error)?;
Ok(Self { db, system })
Ok(Self {
db,
position_encoding,
system,
})
}
#[wasm_bindgen(js_name = "updateOptions")]
@ -216,13 +225,18 @@ impl Workspace {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
let offset = position.to_text_size(&source, &index)?;
let offset = position.to_text_size(&source, &index, self.position_encoding)?;
let Some(targets) = goto_type_definition(&self.db, file_id.file, offset) else {
return Ok(Vec::new());
};
let source_range = Range::from_text_range(targets.file_range().range(), &index, &source);
let source_range = Range::from_text_range(
targets.file_range().range(),
&index,
&source,
self.position_encoding,
);
let links: Vec<_> = targets
.into_iter()
@ -231,10 +245,12 @@ impl Workspace {
full_range: Range::from_file_range(
&self.db,
FileRange::new(target.file(), target.full_range()),
self.position_encoding,
),
selection_range: Some(Range::from_file_range(
&self.db,
FileRange::new(target.file(), target.focus_range()),
self.position_encoding,
)),
origin_selection_range: Some(source_range),
})
@ -248,13 +264,18 @@ impl Workspace {
let source = source_text(&self.db, file_id.file);
let index = line_index(&self.db, file_id.file);
let offset = position.to_text_size(&source, &index)?;
let offset = position.to_text_size(&source, &index, self.position_encoding)?;
let Some(range_info) = hover(&self.db, file_id.file, offset) else {
return Ok(None);
};
let source_range = Range::from_text_range(range_info.file_range().range(), &index, &source);
let source_range = Range::from_text_range(
range_info.file_range().range(),
&index,
&source,
self.position_encoding,
);
Ok(Some(Hover {
markdown: range_info
@ -272,14 +293,19 @@ impl Workspace {
let result = inlay_hints(
&self.db,
file_id.file,
range.to_text_range(&index, &source)?,
range.to_text_range(&index, &source, self.position_encoding)?,
);
Ok(result
.into_iter()
.map(|hint| InlayHint {
markdown: hint.display(&self.db).to_string(),
position: Position::from_text_size(hint.position, &index, &source),
position: Position::from_text_size(
hint.position,
&index,
&source,
self.position_encoding,
),
})
.collect())
}
@ -348,6 +374,7 @@ impl Diagnostic {
Some(Range::from_file_range(
&workspace.db,
FileRange::new(span.file(), span.range()?),
workspace.position_encoding,
))
})
}
@ -378,21 +405,31 @@ impl Range {
}
impl Range {
fn from_file_range(db: &dyn Db, file_range: FileRange) -> Self {
fn from_file_range(
db: &dyn Db,
file_range: FileRange,
position_encoding: PositionEncoding,
) -> Self {
let index = line_index(db.upcast(), file_range.file());
let source = source_text(db.upcast(), file_range.file());
Self::from_text_range(file_range.range(), &index, &source)
Self::from_text_range(file_range.range(), &index, &source, position_encoding)
}
fn from_text_range(
text_range: ruff_text_size::TextRange,
line_index: &LineIndex,
source: &str,
position_encoding: PositionEncoding,
) -> Self {
Self {
start: Position::from_text_size(text_range.start(), line_index, source),
end: Position::from_text_size(text_range.end(), line_index, source),
start: Position::from_text_size(
text_range.start(),
line_index,
source,
position_encoding,
),
end: Position::from_text_size(text_range.end(), line_index, source, position_encoding),
}
}
@ -400,23 +437,19 @@ impl Range {
self,
line_index: &LineIndex,
source: &str,
position_encoding: PositionEncoding,
) -> Result<ruff_text_size::TextRange, Error> {
let start = self.start.to_text_size(source, line_index)?;
let end = self.end.to_text_size(source, line_index)?;
let start = self
.start
.to_text_size(source, line_index, position_encoding)?;
let end = self
.end
.to_text_size(source, line_index, position_encoding)?;
Ok(ruff_text_size::TextRange::new(start, end))
}
}
impl From<(LineColumn, LineColumn)> for Range {
fn from((start, end): (LineColumn, LineColumn)) -> Self {
Self {
start: start.into(),
end: end.into(),
}
}
}
#[wasm_bindgen]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Position {
@ -436,7 +469,12 @@ impl Position {
}
impl Position {
fn to_text_size(self, text: &str, index: &LineIndex) -> Result<TextSize, Error> {
fn to_text_size(
self,
text: &str,
index: &LineIndex,
position_encoding: PositionEncoding,
) -> Result<TextSize, Error> {
let text_size = index.offset(
SourceLocation {
line: OneIndexed::new(self.line).ok_or_else(|| {
@ -451,22 +489,22 @@ impl Position {
})?,
},
text,
PositionEncoding::Utf32,
position_encoding.into(),
);
Ok(text_size)
}
fn from_text_size(offset: TextSize, line_index: &LineIndex, source: &str) -> Self {
line_index.line_column(offset, source).into()
}
}
impl From<LineColumn> for Position {
fn from(location: LineColumn) -> Self {
fn from_text_size(
offset: TextSize,
line_index: &LineIndex,
source: &str,
position_encoding: PositionEncoding,
) -> Self {
let location = line_index.source_location(offset, source, position_encoding.into());
Self {
line: location.line.get(),
column: location.column.get(),
column: location.character_offset.get(),
}
}
}
@ -506,6 +544,25 @@ impl From<ruff_text_size::TextRange> for TextRange {
}
}
#[derive(Default, Copy, Clone)]
#[wasm_bindgen]
pub enum PositionEncoding {
#[default]
Utf8,
Utf16,
Utf32,
}
impl From<PositionEncoding> for ruff_source_file::PositionEncoding {
fn from(value: PositionEncoding) -> Self {
match value {
PositionEncoding::Utf8 => Self::Utf8,
PositionEncoding::Utf16 => Self::Utf16,
PositionEncoding::Utf32 => Self::Utf32,
}
}
}
#[wasm_bindgen]
pub struct LocationLink {
/// The target file path

View file

@ -1,12 +1,16 @@
#![cfg(target_arch = "wasm32")]
use red_knot_wasm::{Position, Workspace};
use red_knot_wasm::{Position, PositionEncoding, Workspace};
use wasm_bindgen_test::wasm_bindgen_test;
#[wasm_bindgen_test]
fn check() {
let mut workspace =
Workspace::new("/", js_sys::JSON::parse("{}").unwrap()).expect("Workspace to be created");
let mut workspace = Workspace::new(
"/",
PositionEncoding::Utf32,
js_sys::JSON::parse("{}").unwrap(),
)
.expect("Workspace to be created");
workspace
.open_file("test.py", "import random22\n")

View file

@ -643,10 +643,9 @@ impl FromStr for OneIndexed {
}
}
#[derive(Default, Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug)]
pub enum PositionEncoding {
/// Character offsets count the number of bytes from the start of the line.
#[default]
Utf8,
/// Character offsets count the number of UTF-16 code units from the start of the line.

View file

@ -10,7 +10,7 @@ import {
useState,
} from "react";
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
import { FileHandle, Workspace } from "red_knot_wasm";
import { FileHandle, PositionEncoding, Workspace } from "red_knot_wasm";
import { persist, persistLocal, restore } from "./Editor/persist";
import { loader } from "@monaco-editor/react";
import knotSchema from "../../../knot.schema.json";
@ -30,7 +30,7 @@ export default function Playground() {
workspacePromiseRef.current = workspacePromise = startPlayground().then(
(fetched) => {
setVersion(fetched.version);
const workspace = new Workspace("/", {});
const workspace = new Workspace("/", PositionEncoding.Utf16, {});
restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
setWorkspace(workspace);
return workspace;