feat: support untitled url scheme for unsaved documents (#120)

This commit is contained in:
Myriad-Dreamin 2024-03-29 15:26:46 +08:00 committed by GitHub
parent d53bd80d14
commit 529b422189
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 202 additions and 100 deletions

View file

@ -37,6 +37,7 @@ reflexo.workspace = true
lsp-types.workspace = true
if_chain = "1"
percent-encoding = "2"
[dev-dependencies]
once_cell.workspace = true

View file

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{path_to_url, prelude::*};
/// Stores diagnostics for files.
pub type DiagnosticsMap = HashMap<Url, Vec<LspDiagnostic>>;
@ -28,20 +28,11 @@ fn convert_diagnostic(
let uri;
let lsp_range;
if let Some((id, span)) = diagnostic_span_id(typst_diagnostic) {
uri = Url::from_file_path(ctx.path_for_id(id)?).map_err(|e| {
let _: () = e;
anyhow::anyhow!(
"could not convert path to URI: id: {id:?}, context: {:?}",
ctx.analysis.root
)
})?;
uri = path_to_url(&ctx.path_for_id(id)?)?;
let source = ctx.world().source(id)?;
lsp_range = diagnostic_range(&source, span, ctx.position_encoding());
} else {
uri = Url::from_file_path(ctx.analysis.root.clone()).map_err(|e| {
let _: () = e;
anyhow::anyhow!("could not convert path to URI: {:?}", ctx.analysis.root)
})?;
uri = path_to_url(&ctx.analysis.root)?;
lsp_range = LspRange::default();
};
@ -72,13 +63,7 @@ fn tracepoint_to_relatedinformation(
position_encoding: PositionEncoding,
) -> anyhow::Result<Option<DiagnosticRelatedInformation>> {
if let Some(id) = tracepoint.span.id() {
let uri = Url::from_file_path(project.path_for_id(id)?).map_err(|e| {
let _: () = e;
anyhow::anyhow!(
"could not convert path to URI: id: {id:?}, context: {:?}",
project.analysis.root
)
})?;
let uri = path_to_url(&project.path_for_id(id)?)?;
let source = project.world().source(id)?;
if let Some(typst_range) = source.range(tracepoint.span) {

View file

@ -60,7 +60,7 @@ impl SemanticRequest for GotoDeclarationRequest {
let span_path = ctx.path_for_id(ref_id).ok()?;
let range = ctx.to_lsp_range(ref_range, ref_source);
let uri = Url::from_file_path(span_path).ok()?;
let uri = path_to_url(&span_path).ok()?;
links.push(LocationLink {
origin_selection_range: Some(origin_selection_range),

View file

@ -54,7 +54,7 @@ impl SemanticRequest for GotoDefinitionRequest {
let def = find_definition(ctx, source.clone(), deref_target)?;
let span_path = ctx.path_for_id(def.fid).ok()?;
let uri = Url::from_file_path(span_path).ok()?;
let uri = path_to_url(&span_path).ok()?;
let span_source = ctx.source_by_id(def.fid).ok()?;
let range = ctx.to_lsp_range(def.def_range, &span_source);

View file

@ -3,7 +3,10 @@
// todo: remove this
#![allow(missing_docs)]
use lsp_types;
use std::path::{Path, PathBuf};
use lsp_types::{self, Url};
use reflexo::path::PathClean;
pub type LspPosition = lsp_types::Position;
/// The interpretation of an `LspCharacterOffset` depends on the
@ -64,6 +67,41 @@ pub type LspCompletionKind = lsp_types::CompletionItemKind;
pub type TypstCompletion = typst_ide::Completion;
pub type TypstCompletionKind = typst_ide::CompletionKind;
const UNTITLED_ROOT: &str = "/untitled";
pub fn path_to_url(path: &Path) -> anyhow::Result<Url> {
if let Ok(untitled) = path.strip_prefix(UNTITLED_ROOT) {
return Ok(Url::parse(&format!("untitled:{}", untitled.display()))?);
}
Url::from_file_path(path).map_err(|e| {
let _: () = e;
anyhow::anyhow!("could not convert path to URI: path: {path:?}",)
})
}
pub fn url_to_path(uri: Url) -> PathBuf {
if uri.scheme() == "file" {
return uri.to_file_path().unwrap();
}
if uri.scheme() == "untitled" {
let mut bytes = UNTITLED_ROOT.as_bytes().to_vec();
// This is rust-url's path_segments, but vscode's untitle doesn't like it.
let path = uri.path();
let segs = path.strip_prefix('/').unwrap_or(path).split('/');
for segment in segs {
bytes.push(b'/');
bytes.extend(percent_encoding::percent_decode(segment.as_bytes()));
}
return Path::new(String::from_utf8_lossy(&bytes).as_ref()).clean();
}
uri.to_file_path().unwrap()
}
pub mod lsp_to_typst {
use typst::syntax::Source;
@ -326,6 +364,17 @@ mod test {
use super::*;
#[test]
fn test_untitled() {
let path = Path::new("/untitled/test");
let uri = path_to_url(path).unwrap();
assert_eq!(uri.scheme(), "untitled");
assert_eq!(uri.path(), "test");
let path = url_to_path(uri);
assert_eq!(path, Path::new("/untitled/test").clean());
}
const ENCODING_TEST_STRING: &str = "test 🥺 test";
#[test]

View file

@ -30,7 +30,7 @@ pub use typst::World;
pub use crate::analysis::{analyze_expr, AnalysisContext};
pub use crate::lsp_typst_boundary::{
lsp_to_typst, typst_to_lsp, LspDiagnostic, LspRange, LspSeverity, PositionEncoding,
TypstDiagnostic, TypstSeverity, TypstSpan,
lsp_to_typst, path_to_url, typst_to_lsp, LspDiagnostic, LspRange, LspSeverity,
PositionEncoding, TypstDiagnostic, TypstSeverity, TypstSpan,
};
pub use crate::VersionedDocument;

View file

@ -116,7 +116,7 @@ pub(crate) fn find_references_root(
) -> Option<Vec<LspLocation>> {
let def_source = ctx.source_by_id(def_fid).ok()?;
let def_path = ctx.path_for_id(def_fid).ok()?;
let uri = Url::from_file_path(def_path).ok()?;
let uri = path_to_url(&def_path).ok()?;
// todo: reuse uri, range to location
let mut references = def_use
@ -140,7 +140,7 @@ pub(crate) fn find_references_root(
let def_use = ctx.ctx.def_use(ref_source.clone())?;
let uri = ctx.ctx.path_for_id(ref_fid).ok()?;
let uri = Url::from_file_path(uri).ok()?;
let uri = path_to_url(&uri).ok()?;
let mut redefines = vec![];
if let Some((id, _def)) = def_use.get_def(def_fid, &def_ident) {
@ -170,7 +170,7 @@ mod tests {
use typst_ts_core::path::unix_slash;
use super::*;
use crate::tests::*;
use crate::{tests::*, url_to_path};
#[test]
fn test() {
@ -196,7 +196,7 @@ mod tests {
let result = result.map(|v| {
v.into_iter()
.map(|l| {
let fp = unix_slash(&l.uri.to_file_path().unwrap());
let fp = unix_slash(&url_to_path(l.uri));
let fp = fp.strip_prefix("C:").unwrap_or(&fp);
format!(
"{fp}@{}:{}:{}:{}",

View file

@ -48,7 +48,7 @@ impl SemanticRequest for RenameRequest {
let def_source = ctx.source_by_id(lnk.fid).ok()?;
let span_path = ctx.path_for_id(lnk.fid).ok()?;
let uri = Url::from_file_path(span_path).ok()?;
let uri = path_to_url(&span_path).ok()?;
let Some(range) = lnk.name_range else {
log::warn!("rename: no name range");

View file

@ -40,7 +40,7 @@ impl SemanticRequest for SymbolRequest {
let Ok(source) = ctx.source_by_path(path) else {
return;
};
let uri = Url::from_file_path(path).unwrap();
let uri = path_to_url(path).unwrap();
let res = get_lexical_hierarchy(source.clone(), LexicalScopeKind::Symbol).and_then(
|symbols| {
self.pattern.as_ref().map(|pattern| {