Skip to content
50 changes: 27 additions & 23 deletions crates/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2198,7 +2198,7 @@ pub enum FsCommand {
pub enum GitCommand {
/// Branch, ahead/behind, stash, and working-tree status
Status {
/// Repository location on the server (default: server cwd)
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand Down Expand Up @@ -2228,7 +2228,7 @@ pub enum GitCommand {
#[arg(last = true)]
pathspec: Vec<String>,

/// Repository location on the server (default: server cwd)
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand Down Expand Up @@ -2284,7 +2284,7 @@ pub enum GitCommand {
#[arg(last = true)]
pathspec: Vec<String>,

/// Repository location on the server (default: server cwd)
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand Down Expand Up @@ -2318,12 +2318,15 @@ pub enum GitCommand {
/// yas git show HEAD:src/main.rs # a file at a revision
/// yas git show v1.0:Cargo.toml
/// yas git show HEAD # the commit object itself
/// yas git show HEAD:src # a directory, listed like ls-tree
Show {
/// REV[:PATH]. Omit PATH for the commit object; omit REV
/// (`:path`) for HEAD.
/// REV[:PATH]. REV names a commit; reach trees and blobs by PATH.
/// Omit PATH for the commit object (in `git cat-file commit`
/// layout); a directory PATH is listed like ls-tree, and `REV:`
/// lists the root tree. Omit REV (`:path`) for HEAD.
spec: String,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand All @@ -2340,7 +2343,7 @@ pub enum GitCommand {
/// REV[:PATH]; omit PATH for the root tree
spec: String,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand All @@ -2358,7 +2361,7 @@ pub enum GitCommand {
#[arg(default_value = "")]
path: String,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand All @@ -2375,7 +2378,7 @@ pub enum GitCommand {
#[arg(required = true, num_args = 2..)]
revs: Vec<String>,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand All @@ -2393,7 +2396,7 @@ pub enum GitCommand {
/// File to blame
path: String,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand Down Expand Up @@ -2428,7 +2431,7 @@ pub enum GitCommand {
#[arg(default_value = "")]
ref_name: String,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand All @@ -2450,7 +2453,7 @@ pub enum GitCommand {
/// TSV: WORKDIR<TAB>GITDIR. Deduped by gitdir, so several paths
/// resolving to one repository report once.
Discover {
/// Directory to search (default: server cwd)
/// Directory to search (relative to the client's cwd)
#[arg(default_value = ".")]
path: String,

Expand Down Expand Up @@ -2484,7 +2487,7 @@ pub enum GitCommand {
/// Refspecs to fetch (default: the remote's configured ones)
refspecs: Vec<String>,

/// Repository path on the server
/// Repository path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
repo: String,

Expand Down Expand Up @@ -2599,7 +2602,7 @@ pub enum LspCommand {
/// Position, 1-based (e.g. src/main.rs:10:4)
spec: String,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2617,7 +2620,7 @@ pub enum LspCommand {
#[arg(long)]
declaration: bool,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2631,7 +2634,7 @@ pub enum LspCommand {
/// Position, 1-based (e.g. src/main.rs:10:4)
spec: String,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2648,7 +2651,7 @@ pub enum LspCommand {
/// Position, 1-based (e.g. src/main.rs:10:4)
spec: String,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2665,7 +2668,7 @@ pub enum LspCommand {
/// Position, 1-based (e.g. src/main.rs:10:4)
spec: String,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2684,7 +2687,7 @@ pub enum LspCommand {
#[arg(long)]
file: Option<String>,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2705,11 +2708,12 @@ pub enum LspCommand {
#[arg(long)]
watch: bool,

/// Wait for language servers to finish indexing first
/// Wait for language servers to finish indexing, then for the next
/// published diagnostics (or 5 seconds without any server update)
#[arg(long)]
wait: bool,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2727,7 +2731,7 @@ pub enum LspCommand {
/// The new name
new_name: String,

/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand All @@ -2738,7 +2742,7 @@ pub enum LspCommand {

/// Block until the workspace's language servers are ready
Wait {
/// Workspace location on the server (default: server cwd)
/// Workspace path on the server (relative to the client's cwd)
#[arg(long, default_value = ".")]
root: String,

Expand Down
17 changes: 11 additions & 6 deletions crates/cli/src/learn.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ yas fs ln -s target link # symlink (omit -s for a hard link)
## Git

Read-only introspection of repositories on the server. `--repo` picks the
worktree (default: cwd); `--json` emits NDJSON.
worktree; a relative path, and the default `.`, resolve against the client's
cwd, as `--root` does for `fs` and `lsp`. `--json` emits NDJSON.

```bash
yas git status # branch, ahead/behind, stash, worktree
Expand All @@ -302,14 +303,15 @@ yas git log # history, newest first
yas git log v1.0 # from a tag
yas git log main..feature # a range
yas git log --follow -- src/main.rs
yas git diff # unstaged
yas git diff # unstaged (-p for hunks)
yas git diff --staged # staged
yas git diff main dev # between two commits
yas git diff main...dev # since they diverged (from the merge base)
yas git diff --merge-base main # worktree vs where main forked (a `base` line names it)
yas git diff HEAD~2 -p -- src # with hunks, limited to a path
yas git show HEAD:src/main.rs # a file's bytes at a revision
yas git show HEAD # the commit object itself
yas git show HEAD:src # a directory, listed like ls-tree
yas git ls-tree HEAD # one tree level (MODE TYPE OID<TAB>NAME)
yas git ls-tree HEAD:src # descend by passing a path
yas git merge-base main feature # best common ancestors (exit 1 if unrelated)
Expand All @@ -335,12 +337,13 @@ having refused one refspec of several).
Language servers (rust-analyzer, gopls, clangd, …) are discovered by
project markers, spawned on the server, and stay warm across
invocations. Positions are 1-based PATH:LINE:COL; all commands take
`--root` (default: server cwd) and `--json` (NDJSON).
`--root` (default: the client's cwd; relative paths resolve against it) and
`--json` (NDJSON).

```bash
yas lsp wait # block until servers finish indexing
yas lsp diag # current diagnostics (exit 1 if any)
yas lsp diag --wait # settle first — use after editing files
yas lsp diag --wait # wait for the next publish — use after editing files
yas lsp def src/main.rs:10:4 # definition of the symbol at 10:4
yas lsp refs src/main.rs:10:4 # references (--declaration to include it)
yas lsp hover src/main.rs:10:4 # type and docs
Expand All @@ -352,9 +355,11 @@ yas lsp rename src/main.rs:10:4 nm # rename plan (prints edits, never applies)
yas lsp list # running servers (ref, phase, memory)
```

A first call in a fresh workspace may exit 2 with "warming up" — run
A first call in a fresh workspace may exit 2 with `Busy: Warming` — run
`yas lsp wait` once, then query. The edit loop: change files, then
`yas lsp diag --wait` to see resulting errors.
`yas lsp diag --wait` to see resulting errors. `--wait` waits for indexing,
then for the next published diagnostics, or 5 seconds without any server
update when nothing changes.

## Key/value store

Expand Down
92 changes: 49 additions & 43 deletions crates/cli/src/yas_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,11 +867,12 @@ async fn cmd_grep(
flags |= yas_wire::schema::fs::GREP_INCLUDE_IGNORED as u16;
}

let file_limit = (max_matches != 0).then_some(usize::from(max_matches));
let mut cursor = Vec::new();
let mut matched_files = 0usize;
let mut hits = 0usize;
let mut truncated = false;
loop {
let mut last_path = None;
let mut truncated;
'pages: loop {
let page: QueryPage = root
.client
.request_typed(
Expand All @@ -880,7 +881,7 @@ async fn cmd_grep(
&Grep {
root_handle: root.handle,
flags,
max_results: max_matches,
max_results: 0,
max_per_file: 0,
query: pattern.as_bytes().to_vec(),
cursor: cursor.clone(),
Expand All @@ -890,7 +891,7 @@ async fn cmd_grep(
true,
)
.await?;
truncated |= page.flags & yas_wire::schema::fs::PAGE_TRUNCATED as u16 != 0;
truncated = page.flags & yas_wire::schema::fs::PAGE_TRUNCATED as u16 != 0;
let next = page.next_cursor.clone();
let records = page_records(&mut root.client, page).await?;
let files: BTreeMap<u32, QueryGrepFileRecord> = records
Expand All @@ -900,39 +901,49 @@ async fn cmd_grep(
_ => None,
})
.collect();
if files_with_matches {
for file in files.values().filter(|file| file.match_count != 0) {
matched_files += 1;
print_grep_file(file, json);
}
} else {
for record in records {
let QueryRecord::GrepMatch(found) = record else {
continue;
};
let file = files
.get(&found.file_index)
.ok_or_else(|| "FS GREP match referenced a missing file".to_string())?;
hits += 1;
let path = display_path(&file.path);
let ignored =
file.flags & yas_wire::schema::fs::QUERY_GREP_FILE_IGNORED as u16 != 0;
if json {
println!(
"{}",
serde_json::json!({
"path": path,
"ignored": ignored,
"line": found.line + 1,
"col": found.column,
"endLine": found.end_line + 1,
"endCol": found.end_column,
"text": found.text,
})
);
} else {
println!("{path}:{}:{}", found.line + 1, found.text);
for record in records {
match record {
QueryRecord::GrepFile(file) if file.match_count != 0 => {
// A file whose matches span a page boundary is sent again
// at the top of the next page.
if last_path.as_ref() == Some(&file.path) {
continue;
}
if file_limit.is_some_and(|limit| matched_files >= limit) {
truncated = false;
break 'pages;
}
matched_files += 1;
if files_with_matches {
print_grep_file(&file, json);
}
last_path = Some(file.path);
}
QueryRecord::GrepMatch(found) if !files_with_matches => {
let file = files
.get(&found.file_index)
.ok_or_else(|| "FS GREP match referenced a missing file".to_string())?;
let path = display_path(&file.path);
let ignored =
file.flags & yas_wire::schema::fs::QUERY_GREP_FILE_IGNORED as u16 != 0;
if json {
println!(
"{}",
serde_json::json!({
"path": path,
"ignored": ignored,
"line": found.line + 1,
"col": found.column,
"endLine": found.end_line + 1,
"endCol": found.end_column,
"text": found.text,
})
);
} else {
println!("{path}:{}:{}", found.line + 1, found.text);
}
}
_ => {}
}
}
if next.is_empty() || next == cursor {
Expand All @@ -944,12 +955,7 @@ async fn cmd_grep(
eprintln!("yas: results truncated — a budget was reached");
}
root.close().await?;
let count = if files_with_matches {
matched_files
} else {
hits
};
Ok(if count == 0 { 1 } else { 0 })
Ok(if matched_files == 0 { 1 } else { 0 })
}

async fn page_records(
Expand Down
Loading
Loading