mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[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
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:
parent
1c65e0ad25
commit
1bdb22c139
4 changed files with 101 additions and 41 deletions
|
@ -19,7 +19,7 @@ use ruff_db::system::{
|
||||||
use ruff_db::Upcast;
|
use ruff_db::Upcast;
|
||||||
use ruff_notebook::Notebook;
|
use ruff_notebook::Notebook;
|
||||||
use ruff_python_formatter::formatted_file;
|
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 ruff_text_size::{Ranged, TextSize};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
@ -42,13 +42,18 @@ pub fn run() {
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
db: ProjectDatabase,
|
db: ProjectDatabase,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
system: WasmSystem,
|
system: WasmSystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
#[wasm_bindgen(constructor)]
|
#[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(
|
let options = Options::deserialize_with(
|
||||||
ValueSource::Cli,
|
ValueSource::Cli,
|
||||||
serde_wasm_bindgen::Deserializer::from(options),
|
serde_wasm_bindgen::Deserializer::from(options),
|
||||||
|
@ -62,7 +67,11 @@ impl Workspace {
|
||||||
|
|
||||||
let db = ProjectDatabase::new(project, system.clone()).map_err(into_error)?;
|
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")]
|
#[wasm_bindgen(js_name = "updateOptions")]
|
||||||
|
@ -216,13 +225,18 @@ impl Workspace {
|
||||||
let source = source_text(&self.db, file_id.file);
|
let source = source_text(&self.db, file_id.file);
|
||||||
let index = line_index(&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 {
|
let Some(targets) = goto_type_definition(&self.db, file_id.file, offset) else {
|
||||||
return Ok(Vec::new());
|
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
|
let links: Vec<_> = targets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -231,10 +245,12 @@ impl Workspace {
|
||||||
full_range: Range::from_file_range(
|
full_range: Range::from_file_range(
|
||||||
&self.db,
|
&self.db,
|
||||||
FileRange::new(target.file(), target.full_range()),
|
FileRange::new(target.file(), target.full_range()),
|
||||||
|
self.position_encoding,
|
||||||
),
|
),
|
||||||
selection_range: Some(Range::from_file_range(
|
selection_range: Some(Range::from_file_range(
|
||||||
&self.db,
|
&self.db,
|
||||||
FileRange::new(target.file(), target.focus_range()),
|
FileRange::new(target.file(), target.focus_range()),
|
||||||
|
self.position_encoding,
|
||||||
)),
|
)),
|
||||||
origin_selection_range: Some(source_range),
|
origin_selection_range: Some(source_range),
|
||||||
})
|
})
|
||||||
|
@ -248,13 +264,18 @@ impl Workspace {
|
||||||
let source = source_text(&self.db, file_id.file);
|
let source = source_text(&self.db, file_id.file);
|
||||||
let index = line_index(&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 {
|
let Some(range_info) = hover(&self.db, file_id.file, offset) else {
|
||||||
return Ok(None);
|
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 {
|
Ok(Some(Hover {
|
||||||
markdown: range_info
|
markdown: range_info
|
||||||
|
@ -272,14 +293,19 @@ impl Workspace {
|
||||||
let result = inlay_hints(
|
let result = inlay_hints(
|
||||||
&self.db,
|
&self.db,
|
||||||
file_id.file,
|
file_id.file,
|
||||||
range.to_text_range(&index, &source)?,
|
range.to_text_range(&index, &source, self.position_encoding)?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(result
|
Ok(result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|hint| InlayHint {
|
.map(|hint| InlayHint {
|
||||||
markdown: hint.display(&self.db).to_string(),
|
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())
|
.collect())
|
||||||
}
|
}
|
||||||
|
@ -348,6 +374,7 @@ impl Diagnostic {
|
||||||
Some(Range::from_file_range(
|
Some(Range::from_file_range(
|
||||||
&workspace.db,
|
&workspace.db,
|
||||||
FileRange::new(span.file(), span.range()?),
|
FileRange::new(span.file(), span.range()?),
|
||||||
|
workspace.position_encoding,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -378,21 +405,31 @@ impl Range {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 index = line_index(db.upcast(), file_range.file());
|
||||||
let source = source_text(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(
|
fn from_text_range(
|
||||||
text_range: ruff_text_size::TextRange,
|
text_range: ruff_text_size::TextRange,
|
||||||
line_index: &LineIndex,
|
line_index: &LineIndex,
|
||||||
source: &str,
|
source: &str,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start: Position::from_text_size(text_range.start(), line_index, source),
|
start: Position::from_text_size(
|
||||||
end: Position::from_text_size(text_range.end(), line_index, source),
|
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,
|
self,
|
||||||
line_index: &LineIndex,
|
line_index: &LineIndex,
|
||||||
source: &str,
|
source: &str,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
) -> Result<ruff_text_size::TextRange, Error> {
|
) -> Result<ruff_text_size::TextRange, Error> {
|
||||||
let start = self.start.to_text_size(source, line_index)?;
|
let start = self
|
||||||
let end = self.end.to_text_size(source, line_index)?;
|
.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))
|
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]
|
#[wasm_bindgen]
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
|
@ -436,7 +469,12 @@ impl Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
let text_size = index.offset(
|
||||||
SourceLocation {
|
SourceLocation {
|
||||||
line: OneIndexed::new(self.line).ok_or_else(|| {
|
line: OneIndexed::new(self.line).ok_or_else(|| {
|
||||||
|
@ -451,22 +489,22 @@ impl Position {
|
||||||
})?,
|
})?,
|
||||||
},
|
},
|
||||||
text,
|
text,
|
||||||
PositionEncoding::Utf32,
|
position_encoding.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(text_size)
|
Ok(text_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_text_size(offset: TextSize, line_index: &LineIndex, source: &str) -> Self {
|
fn from_text_size(
|
||||||
line_index.line_column(offset, source).into()
|
offset: TextSize,
|
||||||
}
|
line_index: &LineIndex,
|
||||||
}
|
source: &str,
|
||||||
|
position_encoding: PositionEncoding,
|
||||||
impl From<LineColumn> for Position {
|
) -> Self {
|
||||||
fn from(location: LineColumn) -> Self {
|
let location = line_index.source_location(offset, source, position_encoding.into());
|
||||||
Self {
|
Self {
|
||||||
line: location.line.get(),
|
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]
|
#[wasm_bindgen]
|
||||||
pub struct LocationLink {
|
pub struct LocationLink {
|
||||||
/// The target file path
|
/// The target file path
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
#![cfg(target_arch = "wasm32")]
|
#![cfg(target_arch = "wasm32")]
|
||||||
|
|
||||||
use red_knot_wasm::{Position, Workspace};
|
use red_knot_wasm::{Position, PositionEncoding, Workspace};
|
||||||
use wasm_bindgen_test::wasm_bindgen_test;
|
use wasm_bindgen_test::wasm_bindgen_test;
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
fn check() {
|
fn check() {
|
||||||
let mut workspace =
|
let mut workspace = Workspace::new(
|
||||||
Workspace::new("/", js_sys::JSON::parse("{}").unwrap()).expect("Workspace to be created");
|
"/",
|
||||||
|
PositionEncoding::Utf32,
|
||||||
|
js_sys::JSON::parse("{}").unwrap(),
|
||||||
|
)
|
||||||
|
.expect("Workspace to be created");
|
||||||
|
|
||||||
workspace
|
workspace
|
||||||
.open_file("test.py", "import random22\n")
|
.open_file("test.py", "import random22\n")
|
||||||
|
|
|
@ -643,10 +643,9 @@ impl FromStr for OneIndexed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PositionEncoding {
|
pub enum PositionEncoding {
|
||||||
/// Character offsets count the number of bytes from the start of the line.
|
/// Character offsets count the number of bytes from the start of the line.
|
||||||
#[default]
|
|
||||||
Utf8,
|
Utf8,
|
||||||
|
|
||||||
/// Character offsets count the number of UTF-16 code units from the start of the line.
|
/// Character offsets count the number of UTF-16 code units from the start of the line.
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { ErrorMessage, Header, setupMonaco, useTheme } from "shared";
|
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 { persist, persistLocal, restore } from "./Editor/persist";
|
||||||
import { loader } from "@monaco-editor/react";
|
import { loader } from "@monaco-editor/react";
|
||||||
import knotSchema from "../../../knot.schema.json";
|
import knotSchema from "../../../knot.schema.json";
|
||||||
|
@ -30,7 +30,7 @@ export default function Playground() {
|
||||||
workspacePromiseRef.current = workspacePromise = startPlayground().then(
|
workspacePromiseRef.current = workspacePromise = startPlayground().then(
|
||||||
(fetched) => {
|
(fetched) => {
|
||||||
setVersion(fetched.version);
|
setVersion(fetched.version);
|
||||||
const workspace = new Workspace("/", {});
|
const workspace = new Workspace("/", PositionEncoding.Utf16, {});
|
||||||
restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
|
restoreWorkspace(workspace, fetched.workspace, dispatchFiles, setError);
|
||||||
setWorkspace(workspace);
|
setWorkspace(workspace);
|
||||||
return workspace;
|
return workspace;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue