git-ents.gitmain
⌘K
foforge
document.rs92 lines · 3.5 KB · rusthistorycomment on this file
1//! The lens's view of the client's open buffers, and the file-URI ↔
2//! repository-path arithmetic that ties an LSP document to the anchor
3//! paths `refs/meta/comments/*` records.
4//!
5//! The lens caches nothing derived — no projection, no lens, no diagnostic
6//! survives a comment-ref mutation (`lens.lenses`) — but it must remember
7//! the *buffer text* the client has sent, because the client owns the only
8//! copy of a document's unsaved content: `textDocument/didChange` ships an
9//! edit, never the file, and the disk still holds the old bytes. Holding
10//! the latest buffer per open URI is what lets projection target the bytes
11//! the user is actually looking at (`lens.working-tree`), and it is dropped
12//! the moment the client closes the document.
13
14use std::collections::HashMap;
15use std::path::{Path, PathBuf};
16
17use lsp_types::Url;
18
19/// The client's open text documents, keyed by URI, each holding the latest
20/// full text the client has sent (`textDocumentSync` is full-text, so every
21/// change replaces the whole buffer).
22#[derive(Debug, Default)]
23pub struct Documents {
24 open: HashMap<Url, String>,
25}
26
27impl Documents {
28 /// Record (or replace) the full text of the document at `uri` — the
29 /// `didOpen`/`didChange` handler's whole job.
30 pub fn set(&mut self, uri: Url, text: String) {
31 self.open.insert(uri, text);
32 }
33
34 /// Forget the document at `uri` — `didClose`; projection falls back to
35 /// the on-disk bytes afterward.
36 pub fn remove(&mut self, uri: &Url) {
37 self.open.remove(uri);
38 }
39
40 /// The latest buffer text for `uri`, if the client has it open.
41 #[must_use]
42 pub fn text(&self, uri: &Url) -> Option<&str> {
43 self.open.get(uri).map(String::as_str)
44 }
45
46 /// Every open document's URI — the server republishes diagnostics for
47 /// these after a comment-ref mutation.
48 #[must_use]
49 pub fn open_uris(&self) -> Vec<Url> {
50 self.open.keys().cloned().collect()
51 }
52}
53
54/// The repository-relative, forward-slashed path a file URI names inside
55/// the working tree at `workdir`, or `None` when the URI is not a file
56/// under it — the key that matches an [`ents_anchor::Anchor`]'s own
57/// recorded path.
58///
59/// Both sides are canonicalized when possible (the working tree always
60/// exists; an open document usually does), so a symlinked temp directory
61/// like macOS's `/var` → `/private/var` does not defeat the prefix match;
62/// when the document has no on-disk form yet, the raw paths are compared.
63#[must_use]
64pub fn relative_path(workdir: &Path, uri: &Url) -> Option<String> {
65 let file = uri.to_file_path().ok()?;
66 let file_canon = canonical(&file);
67 let workdir_canon = canonical(workdir);
68 let rel = file_canon.strip_prefix(&workdir_canon).ok()?;
69 Some(rel.to_string_lossy().replace('\\', "/"))
70}
71
72/// Resolve `path` through symlinks even when its leaf does not exist yet,
73/// by canonicalizing the deepest ancestor that does and re-appending the
74/// rest — so a not-yet-saved document under a symlinked temp directory
75/// (macOS's `/var` → `/private/var`) still shares a prefix with the
76/// canonicalized working tree.
77fn canonical(path: &Path) -> PathBuf {
78 if let Ok(resolved) = path.canonicalize() {
79 return resolved;
80 }
81 match (path.parent(), path.file_name()) {
82 (Some(parent), Some(name)) => canonical(parent).join(name),
83 _ => path.to_owned(),
84 }
85}
86
87/// The absolute file URI for `path` — used to open the compose template
88/// with `window/showDocument`.
89#[must_use]
90pub fn file_uri(path: &Path) -> Option<Url> {
91 Url::from_file_path(path).ok()
92}