crates/cli/ents-lens/src/lib.rs
| 1 | //! The lens: an editor-facing Language Server Protocol surface over |
| 2 | //! `refs/meta/comments/*`, projecting the repository's anchored comments |
| 3 | //! into whatever buffer the user is reading and writing new ones back |
| 4 | //! through the same signed mutation path every other frontend uses. |
| 5 | //! |
| 6 | //! # One responsibility |
| 7 | //! |
| 8 | //! This crate is the third place a git-ents conversation surfaces, after |
| 9 | //! the CLI and the web UI, and it earns no third mechanism (`docs/spec/lens.adoc`): |
| 10 | //! it is a read-time *view* over `refs/meta/*`, owning no state of its own, |
| 11 | //! so a comment left in an editor, on the web, or by an agent at the CLI is |
| 12 | //! one and the same entity everywhere. Every listing, projection, and write |
| 13 | //! is the exact `ents_forge::comment` library call the `git ents comment` |
| 14 | //! porcelain makes (`lens.parity`); the lens never shells out and never |
| 15 | //! reimplements listing or projection. It is a frontend of the local root |
| 16 | //! and receives its signing identity by injection (`lens.serve`, |
| 17 | //! `roots.web-agnostic`), exactly as `ents-web` does. |
| 18 | //! |
| 19 | //! # Spec coverage (`docs/spec/lens.adoc`) |
| 20 | //! |
| 21 | //! - `lens.serve` — [`serve_stdio`], stdio only, no socket, no git |
| 22 | //! transport; the signing identity is the injected [`Signing`]. |
| 23 | //! - `lens.lenses` — [`Lens::code_lenses`]: one View/Reply/Resolve lens set |
| 24 | //! per open comment projecting onto the document, derived per request. |
| 25 | //! - `lens.diagnostics` — [`Lens::diagnostics`]: the same comments as |
| 26 | //! hint-severity diagnostics, never warnings or errors. |
| 27 | //! - `lens.hover` — [`Lens::hover`]: the full thread as Markdown, authorship |
| 28 | //! read from each ref's commit chain. |
| 29 | //! - `lens.compose` — [`Lens::code_actions`] plus the compose flow in |
| 30 | //! [`compose`]: a code action opens a git-style template file, saving a |
| 31 | //! non-empty body creates the comment. |
| 32 | //! - `lens.working-tree` — projection targets the working tree, the open |
| 33 | //! buffer standing in for disk, re-projected on every change. |
| 34 | //! - `lens.parity` — every operation is an `ents_forge::comment` call. |
| 35 | //! |
| 36 | //! # The compose-on-save mechanism |
| 37 | //! |
| 38 | //! Composing works entirely through standard LSP a plain client provides — |
| 39 | //! `workspace/executeCommand`, `window/showDocument`, and |
| 40 | //! `textDocument/didSave` — with no client-specific extension. The |
| 41 | //! `ents.compose` command writes a git-commit-style template under |
| 42 | //! `.git/ENTS_COMMENT_EDITMSG` and asks the client to open it; when the user |
| 43 | //! saves it, the `didSave` handler creates the comment (or aborts on an |
| 44 | //! empty body). See [`compose`] for the exact grammar and rationale. |
| 45 | //! |
| 46 | //! # Worked example |
| 47 | //! |
| 48 | //! Wire a lens against a fresh repository (as `git ents lsp`'s composition |
| 49 | //! root does) and ask it for the code lenses on a document — none yet, since |
| 50 | //! no comment has been written: |
| 51 | //! |
| 52 | //! ``` |
| 53 | //! use ents_lens::{Lens, Signing}; |
| 54 | //! use ents_receive::{Mode, NullEventSink}; |
| 55 | //! use ents_testutil::{MemRefStore, ObjectStore}; |
| 56 | //! |
| 57 | //! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 58 | //! let dir = tempfile::tempdir()?; |
| 59 | //! gix::init(dir.path())?; |
| 60 | //! |
| 61 | //! // The composition root injects the signing identity (`lens.serve`); a |
| 62 | //! // fixed fixture stands in for the user's own resolved key here. |
| 63 | //! let signing = Signing::new( |
| 64 | //! gix::actor::Signature { |
| 65 | //! name: "jdc".into(), |
| 66 | //! email: "jdc@ents.test".into(), |
| 67 | //! time: gix::date::Time { seconds: 0, offset: 0 }, |
| 68 | //! }, |
| 69 | //! Box::new(|_payload| "-----BEGIN SSH SIGNATURE-----\n-----END SSH SIGNATURE-----\n".to_owned()), |
| 70 | //! "ssh-ed25519 AAAA jdc".to_owned(), |
| 71 | //! ); |
| 72 | //! |
| 73 | //! let lens = Lens::new( |
| 74 | //! Box::new(MemRefStore::default()), |
| 75 | //! ObjectStore::default(), |
| 76 | //! Box::new(NullEventSink), |
| 77 | //! Mode::Advisory, |
| 78 | //! signing, |
| 79 | //! dir.path().to_owned(), |
| 80 | //! ); |
| 81 | //! |
| 82 | //! let uri = lsp_types::Url::from_file_path(dir.path().join("src/lib.rs")).unwrap(); |
| 83 | //! assert!(lens.code_lenses(&uri)?.is_empty()); |
| 84 | //! assert!(lens.diagnostics(&uri)?.is_empty()); |
| 85 | //! # Ok(()) |
| 86 | //! # } |
| 87 | //! ``` |
| 88 | |
| 89 | pub mod compose; |
| 90 | mod document; |
| 91 | mod error; |
| 92 | mod lens; |
| 93 | mod render; |
| 94 | mod server; |
| 95 | mod signing; |
| 96 | |
| 97 | pub use compose::{Composed, Target}; |
| 98 | pub use error::{Error, Result}; |
| 99 | pub use lens::{Lens, Outcome}; |
| 100 | pub use render::{CMD_COMPOSE, CMD_REPLY, CMD_RESOLVE, CMD_VIEW}; |
| 101 | pub use server::{capabilities, serve_stdio}; |
| 102 | pub use signing::Signing; |