mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
feat: support untitled url scheme for unsaved documents (#120)
This commit is contained in:
parent
d53bd80d14
commit
529b422189
17 changed files with 202 additions and 100 deletions
|
@ -37,6 +37,7 @@ reflexo.workspace = true
|
|||
|
||||
lsp-types.workspace = true
|
||||
if_chain = "1"
|
||||
percent-encoding = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell.workspace = true
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}@{}:{}:{}:{}",
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue