edit prediction cli: Cargo-style progress output (#44675)
Release Notes: - N/A
This commit is contained in:
Generated
+1
@@ -5179,6 +5179,7 @@ dependencies = [
|
||||
"language_model",
|
||||
"language_models",
|
||||
"languages",
|
||||
"libc",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"paths",
|
||||
|
||||
@@ -34,6 +34,7 @@ language_extension.workspace = true
|
||||
language_model.workspace = true
|
||||
language_models.workspace = true
|
||||
languages = { workspace = true, features = ["load-grammars"] }
|
||||
libc.workspace = true
|
||||
log.workspace = true
|
||||
node_runtime.workspace = true
|
||||
paths.workspace = true
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{
|
||||
example::{Example, ExamplePrompt},
|
||||
headless::EpAppState,
|
||||
load_project::run_load_project,
|
||||
progress::{Progress, Step},
|
||||
retrieve_context::run_context_retrieval,
|
||||
};
|
||||
use edit_prediction::{
|
||||
@@ -17,9 +18,12 @@ pub async fn run_format_prompt(
|
||||
example: &mut Example,
|
||||
prompt_format: PromptFormat,
|
||||
app_state: Arc<EpAppState>,
|
||||
progress: Arc<Progress>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
run_context_retrieval(example, app_state.clone(), cx.clone()).await;
|
||||
run_context_retrieval(example, app_state.clone(), progress.clone(), cx.clone()).await;
|
||||
|
||||
let _step_progress = progress.start(Step::FormatPrompt, &example.name);
|
||||
|
||||
match prompt_format {
|
||||
PromptFormat::Teacher => {
|
||||
@@ -31,7 +35,7 @@ pub async fn run_format_prompt(
|
||||
});
|
||||
}
|
||||
PromptFormat::Zeta2 => {
|
||||
run_load_project(example, app_state, cx.clone()).await;
|
||||
run_load_project(example, app_state, progress.clone(), cx.clone()).await;
|
||||
|
||||
let ep_store = cx
|
||||
.update(|cx| EditPredictionStore::try_global(cx).unwrap())
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
example::{Example, ExampleBuffer, ExampleState},
|
||||
headless::EpAppState,
|
||||
paths::{REPOS_DIR, WORKTREES_DIR},
|
||||
progress::{InfoStyle, Progress, Step, StepProgress},
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use collections::HashMap;
|
||||
@@ -24,30 +25,47 @@ use std::{
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
use zeta_prompt::CURSOR_MARKER;
|
||||
|
||||
pub async fn run_load_project(example: &mut Example, app_state: Arc<EpAppState>, mut cx: AsyncApp) {
|
||||
pub async fn run_load_project(
|
||||
example: &mut Example,
|
||||
app_state: Arc<EpAppState>,
|
||||
progress: Arc<Progress>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
if example.state.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let project = setup_project(example, &app_state, &mut cx).await;
|
||||
let progress = progress.start(Step::LoadProject, &example.name);
|
||||
|
||||
let project = setup_project(example, &app_state, &progress, &mut cx).await;
|
||||
|
||||
let _open_buffers = apply_edit_history(example, &project, &mut cx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (buffer, cursor_position) = cursor_position(example, &project, &mut cx).await;
|
||||
example.buffer = buffer
|
||||
let (example_buffer, language_name) = buffer
|
||||
.read_with(&cx, |buffer, _cx| {
|
||||
let cursor_point = cursor_position.to_point(&buffer);
|
||||
Some(ExampleBuffer {
|
||||
content: buffer.text(),
|
||||
cursor_row: cursor_point.row,
|
||||
cursor_column: cursor_point.column,
|
||||
cursor_offset: cursor_position.to_offset(&buffer),
|
||||
})
|
||||
let language_name = buffer
|
||||
.language()
|
||||
.map(|l| l.name().to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
(
|
||||
ExampleBuffer {
|
||||
content: buffer.text(),
|
||||
cursor_row: cursor_point.row,
|
||||
cursor_column: cursor_point.column,
|
||||
cursor_offset: cursor_position.to_offset(&buffer),
|
||||
},
|
||||
language_name,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
progress.set_info(language_name, InfoStyle::Normal);
|
||||
|
||||
example.buffer = Some(example_buffer);
|
||||
example.state = Some(ExampleState {
|
||||
buffer,
|
||||
project,
|
||||
@@ -131,13 +149,14 @@ async fn cursor_position(
|
||||
async fn setup_project(
|
||||
example: &mut Example,
|
||||
app_state: &Arc<EpAppState>,
|
||||
step_progress: &Arc<StepProgress>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Entity<Project> {
|
||||
let ep_store = cx
|
||||
.update(|cx| EditPredictionStore::try_global(cx).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let worktree_path = setup_worktree(example).await;
|
||||
let worktree_path = setup_worktree(example, step_progress).await;
|
||||
|
||||
if let Some(project) = app_state.project_cache.get(&example.repository_url) {
|
||||
ep_store
|
||||
@@ -158,7 +177,7 @@ async fn setup_project(
|
||||
.update(cx, |buffer, cx| buffer.reload(cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
.ok();
|
||||
}
|
||||
return project;
|
||||
}
|
||||
@@ -208,7 +227,7 @@ async fn setup_project(
|
||||
project
|
||||
}
|
||||
|
||||
pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
async fn setup_worktree(example: &Example, step_progress: &Arc<StepProgress>) -> PathBuf {
|
||||
let (repo_owner, repo_name) = example.repo_name().expect("failed to get repo name");
|
||||
let repo_dir = REPOS_DIR.join(repo_owner.as_ref()).join(repo_name.as_ref());
|
||||
let worktree_path = WORKTREES_DIR
|
||||
@@ -217,7 +236,7 @@ pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
let repo_lock = lock_repo(&repo_dir).await;
|
||||
|
||||
if !repo_dir.is_dir() {
|
||||
eprintln!("Cloning repository {}", example.repository_url);
|
||||
step_progress.set_substatus(format!("cloning {}", repo_name));
|
||||
fs::create_dir_all(&repo_dir).unwrap();
|
||||
run_git(&repo_dir, &["init"]).await.unwrap();
|
||||
run_git(
|
||||
@@ -237,6 +256,7 @@ pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
let revision = if let Ok(revision) = revision {
|
||||
revision
|
||||
} else {
|
||||
step_progress.set_substatus("fetching");
|
||||
if run_git(
|
||||
&repo_dir,
|
||||
&["fetch", "--depth", "1", "origin", &example.revision],
|
||||
@@ -253,6 +273,7 @@ pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
};
|
||||
|
||||
// Create the worktree for this example if needed.
|
||||
step_progress.set_substatus("preparing worktree");
|
||||
if worktree_path.is_dir() {
|
||||
run_git(&worktree_path, &["clean", "--force", "-d"])
|
||||
.await
|
||||
@@ -288,6 +309,7 @@ pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
|
||||
// Apply the uncommitted diff for this example.
|
||||
if !example.uncommitted_diff.is_empty() {
|
||||
step_progress.set_substatus("applying diff");
|
||||
let mut apply_process = smol::process::Command::new("git")
|
||||
.current_dir(&worktree_path)
|
||||
.args(&["apply", "-"])
|
||||
@@ -314,6 +336,7 @@ pub async fn setup_worktree(example: &Example) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
step_progress.clear_substatus();
|
||||
worktree_path
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ mod load_project;
|
||||
mod metrics;
|
||||
mod paths;
|
||||
mod predict;
|
||||
mod progress;
|
||||
mod retrieve_context;
|
||||
mod score;
|
||||
|
||||
@@ -15,8 +16,6 @@ use edit_prediction::EditPredictionStore;
|
||||
use gpui::Application;
|
||||
use reqwest_client::ReqwestClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::distill::run_distill;
|
||||
@@ -24,6 +23,7 @@ use crate::example::{group_examples_by_repo, read_examples, write_examples};
|
||||
use crate::format_prompt::run_format_prompt;
|
||||
use crate::load_project::run_load_project;
|
||||
use crate::predict::run_prediction;
|
||||
use crate::progress::Progress;
|
||||
use crate::retrieve_context::run_context_retrieval;
|
||||
use crate::score::run_scoring;
|
||||
|
||||
@@ -112,7 +112,7 @@ impl EpArgs {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
zlog::init();
|
||||
let _ = zlog::try_init(Some("error".into()));
|
||||
zlog::init_output_stderr();
|
||||
let args = EpArgs::parse();
|
||||
|
||||
@@ -151,33 +151,41 @@ fn main() {
|
||||
predict::sync_batches(&args.provider).await
|
||||
};
|
||||
|
||||
let example_count = examples.len();
|
||||
let example_ix = AtomicUsize::new(0);
|
||||
let mut grouped_examples = group_examples_by_repo(&mut examples);
|
||||
let total_examples = examples.len();
|
||||
let progress = Progress::new(total_examples);
|
||||
|
||||
let mut grouped_examples = group_examples_by_repo(&mut examples);
|
||||
let example_batches = grouped_examples.chunks_mut(args.max_parallelism);
|
||||
|
||||
for example_batch in example_batches {
|
||||
let futures = example_batch.into_iter().map(|repo_examples| async {
|
||||
for example in repo_examples.iter_mut() {
|
||||
eprintln!(
|
||||
"Processing example: {}/{}",
|
||||
example_ix.load(SeqCst) + 1,
|
||||
example_count
|
||||
);
|
||||
example_ix.fetch_add(1, SeqCst);
|
||||
match &command {
|
||||
Command::ParseExample => {}
|
||||
Command::LoadProject => {
|
||||
run_load_project(example, app_state.clone(), cx.clone()).await;
|
||||
run_load_project(
|
||||
example,
|
||||
app_state.clone(),
|
||||
progress.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Command::Context => {
|
||||
run_context_retrieval(example, app_state.clone(), cx.clone()).await;
|
||||
run_context_retrieval(
|
||||
example,
|
||||
app_state.clone(),
|
||||
progress.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Command::FormatPrompt(args) => {
|
||||
run_format_prompt(
|
||||
example,
|
||||
args.prompt_format,
|
||||
app_state.clone(),
|
||||
progress.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
@@ -188,6 +196,7 @@ fn main() {
|
||||
Some(args.provider),
|
||||
args.repetitions,
|
||||
app_state.clone(),
|
||||
progress.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
@@ -196,7 +205,14 @@ fn main() {
|
||||
run_distill(example).await;
|
||||
}
|
||||
Command::Score(args) | Command::Eval(args) => {
|
||||
run_scoring(example, &args, app_state.clone(), cx.clone()).await;
|
||||
run_scoring(
|
||||
example,
|
||||
&args,
|
||||
app_state.clone(),
|
||||
progress.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Command::Clean => {
|
||||
unreachable!()
|
||||
@@ -206,6 +222,7 @@ fn main() {
|
||||
});
|
||||
futures::future::join_all(futures).await;
|
||||
}
|
||||
progress.clear();
|
||||
|
||||
if args.output.is_some() || !matches!(command, Command::Eval(_)) {
|
||||
write_examples(&examples, output.as_ref());
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
headless::EpAppState,
|
||||
load_project::run_load_project,
|
||||
paths::{LATEST_EXAMPLE_RUN_DIR, RUN_DIR},
|
||||
progress::{InfoStyle, Progress, Step},
|
||||
retrieve_context::run_context_retrieval,
|
||||
};
|
||||
use edit_prediction::{DebugEvent, EditPredictionStore};
|
||||
@@ -24,29 +25,41 @@ pub async fn run_prediction(
|
||||
provider: Option<PredictionProvider>,
|
||||
repetition_count: usize,
|
||||
app_state: Arc<EpAppState>,
|
||||
progress: Arc<Progress>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
if !example.predictions.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
run_context_retrieval(example, app_state.clone(), cx.clone()).await;
|
||||
|
||||
let provider = provider.unwrap();
|
||||
|
||||
run_context_retrieval(example, app_state.clone(), progress.clone(), cx.clone()).await;
|
||||
|
||||
if matches!(
|
||||
provider,
|
||||
PredictionProvider::Teacher | PredictionProvider::TeacherNonBatching
|
||||
) {
|
||||
let _step_progress = progress.start(Step::Predict, &example.name);
|
||||
|
||||
if example.prompt.is_none() {
|
||||
run_format_prompt(example, PromptFormat::Teacher, app_state.clone(), cx).await;
|
||||
run_format_prompt(
|
||||
example,
|
||||
PromptFormat::Teacher,
|
||||
app_state.clone(),
|
||||
progress,
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let batched = matches!(provider, PredictionProvider::Teacher);
|
||||
return predict_anthropic(example, repetition_count, batched).await;
|
||||
}
|
||||
|
||||
run_load_project(example, app_state.clone(), cx.clone()).await;
|
||||
run_load_project(example, app_state.clone(), progress.clone(), cx.clone()).await;
|
||||
|
||||
let _step_progress = progress.start(Step::Predict, &example.name);
|
||||
|
||||
if matches!(
|
||||
provider,
|
||||
@@ -181,18 +194,31 @@ pub async fn run_prediction(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actual_patch = prediction
|
||||
.and_then(|prediction| {
|
||||
let prediction = prediction.prediction.ok()?;
|
||||
prediction.edit_preview.as_unified_diff(&prediction.edits)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let has_prediction = !actual_patch.is_empty();
|
||||
|
||||
updated_example
|
||||
.lock()
|
||||
.unwrap()
|
||||
.predictions
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.actual_patch = prediction
|
||||
.and_then(|prediction| {
|
||||
let prediction = prediction.prediction.ok()?;
|
||||
prediction.edit_preview.as_unified_diff(&prediction.edits)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.actual_patch = actual_patch;
|
||||
|
||||
if ix == repetition_count - 1 {
|
||||
let (info, style) = if has_prediction {
|
||||
("predicted", InfoStyle::Normal)
|
||||
} else {
|
||||
("no prediction", InfoStyle::Warning)
|
||||
};
|
||||
_step_progress.set_info(info, style);
|
||||
}
|
||||
}
|
||||
|
||||
ep_store
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
io::{IsTerminal, Write},
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub struct Progress {
|
||||
inner: Mutex<ProgressInner>,
|
||||
}
|
||||
|
||||
struct ProgressInner {
|
||||
completed: Vec<CompletedTask>,
|
||||
in_progress: HashMap<String, InProgressTask>,
|
||||
is_tty: bool,
|
||||
terminal_width: usize,
|
||||
max_example_name_len: usize,
|
||||
status_lines_displayed: usize,
|
||||
total_examples: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InProgressTask {
|
||||
step: Step,
|
||||
started_at: Instant,
|
||||
substatus: Option<String>,
|
||||
info: Option<(String, InfoStyle)>,
|
||||
}
|
||||
|
||||
struct CompletedTask {
|
||||
step: Step,
|
||||
example_name: String,
|
||||
duration: Duration,
|
||||
info: Option<(String, InfoStyle)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Step {
|
||||
LoadProject,
|
||||
Context,
|
||||
FormatPrompt,
|
||||
Predict,
|
||||
Score,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum InfoStyle {
|
||||
Normal,
|
||||
Warning,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Step::LoadProject => "Load",
|
||||
Step::Context => "Context",
|
||||
Step::FormatPrompt => "Format",
|
||||
Step::Predict => "Predict",
|
||||
Step::Score => "Score",
|
||||
}
|
||||
}
|
||||
|
||||
fn color_code(&self) -> &'static str {
|
||||
match self {
|
||||
Step::LoadProject => "\x1b[33m",
|
||||
Step::Context => "\x1b[35m",
|
||||
Step::FormatPrompt => "\x1b[34m",
|
||||
Step::Predict => "\x1b[32m",
|
||||
Step::Score => "\x1b[31m",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const RIGHT_MARGIN: usize = 4;
|
||||
|
||||
impl Progress {
|
||||
pub fn new(total_examples: usize) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
inner: Mutex::new(ProgressInner {
|
||||
completed: Vec::new(),
|
||||
in_progress: HashMap::new(),
|
||||
is_tty: std::io::stderr().is_terminal(),
|
||||
terminal_width: get_terminal_width(),
|
||||
max_example_name_len: 0,
|
||||
status_lines_displayed: 0,
|
||||
total_examples,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(self: &Arc<Self>, step: Step, example_name: &str) -> Arc<StepProgress> {
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
Self::clear_status_lines(&mut inner);
|
||||
|
||||
inner.max_example_name_len = inner.max_example_name_len.max(example_name.len());
|
||||
|
||||
inner.in_progress.insert(
|
||||
example_name.to_string(),
|
||||
InProgressTask {
|
||||
step,
|
||||
started_at: Instant::now(),
|
||||
substatus: None,
|
||||
info: None,
|
||||
},
|
||||
);
|
||||
|
||||
Self::print_status_lines(&mut inner);
|
||||
}
|
||||
|
||||
Arc::new(StepProgress {
|
||||
progress: self.clone(),
|
||||
step,
|
||||
example_name: example_name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish(&self, step: Step, example_name: &str) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
let task = inner.in_progress.remove(example_name);
|
||||
if let Some(task) = task {
|
||||
if task.step == step {
|
||||
inner.completed.push(CompletedTask {
|
||||
step: task.step,
|
||||
example_name: example_name.to_string(),
|
||||
duration: task.started_at.elapsed(),
|
||||
info: task.info,
|
||||
});
|
||||
|
||||
Self::clear_status_lines(&mut inner);
|
||||
Self::print_completed(&inner, inner.completed.last().unwrap());
|
||||
Self::print_status_lines(&mut inner);
|
||||
} else {
|
||||
inner.in_progress.insert(example_name.to_string(), task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_status_lines(inner: &mut ProgressInner) {
|
||||
if inner.is_tty && inner.status_lines_displayed > 0 {
|
||||
// Move up and clear each line we previously displayed
|
||||
for _ in 0..inner.status_lines_displayed {
|
||||
eprint!("\x1b[A\x1b[K");
|
||||
}
|
||||
let _ = std::io::stderr().flush();
|
||||
inner.status_lines_displayed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn print_completed(inner: &ProgressInner, task: &CompletedTask) {
|
||||
let duration = format_duration(task.duration);
|
||||
let name_width = inner.max_example_name_len;
|
||||
|
||||
if inner.is_tty {
|
||||
let reset = "\x1b[0m";
|
||||
let bold = "\x1b[1m";
|
||||
let dim = "\x1b[2m";
|
||||
|
||||
let yellow = "\x1b[33m";
|
||||
let info_part = task
|
||||
.info
|
||||
.as_ref()
|
||||
.map(|(s, style)| {
|
||||
if *style == InfoStyle::Warning {
|
||||
format!("{yellow}{s}{reset}")
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let prefix = format!(
|
||||
"{bold}{color}{label:>12}{reset} {name:<name_width$} {dim}│{reset} {info_part}",
|
||||
color = task.step.color_code(),
|
||||
label = task.step.label(),
|
||||
name = task.example_name,
|
||||
);
|
||||
|
||||
let duration_with_margin = format!("{duration} ");
|
||||
let padding_needed = inner
|
||||
.terminal_width
|
||||
.saturating_sub(RIGHT_MARGIN)
|
||||
.saturating_sub(duration_with_margin.len())
|
||||
.saturating_sub(strip_ansi_len(&prefix));
|
||||
let padding = " ".repeat(padding_needed);
|
||||
|
||||
eprintln!("{prefix}{padding}{dim}{duration_with_margin}{reset}");
|
||||
} else {
|
||||
let info_part = task
|
||||
.info
|
||||
.as_ref()
|
||||
.map(|(s, _)| format!(" | {}", s))
|
||||
.unwrap_or_default();
|
||||
|
||||
eprintln!(
|
||||
"{label:>12} {name:<name_width$}{info_part} {duration}",
|
||||
label = task.step.label(),
|
||||
name = task.example_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_status_lines(inner: &mut ProgressInner) {
|
||||
if !inner.is_tty || inner.in_progress.is_empty() {
|
||||
inner.status_lines_displayed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let reset = "\x1b[0m";
|
||||
let bold = "\x1b[1m";
|
||||
let dim = "\x1b[2m";
|
||||
|
||||
// Build the done/in-progress/total label
|
||||
let done_count = inner.completed.len();
|
||||
let in_progress_count = inner.in_progress.len();
|
||||
let range_label = format!(
|
||||
" {}/{}/{} ",
|
||||
done_count, in_progress_count, inner.total_examples
|
||||
);
|
||||
|
||||
// Print a divider line with range label aligned with timestamps
|
||||
let range_visible_len = range_label.len();
|
||||
let left_divider_len = inner
|
||||
.terminal_width
|
||||
.saturating_sub(RIGHT_MARGIN)
|
||||
.saturating_sub(range_visible_len);
|
||||
let left_divider = "─".repeat(left_divider_len);
|
||||
let right_divider = "─".repeat(RIGHT_MARGIN);
|
||||
eprintln!("{dim}{left_divider}{reset}{range_label}{dim}{right_divider}{reset}");
|
||||
|
||||
let mut tasks: Vec<_> = inner.in_progress.iter().collect();
|
||||
tasks.sort_by_key(|(name, _)| *name);
|
||||
|
||||
let mut lines_printed = 0;
|
||||
|
||||
for (name, task) in tasks.iter() {
|
||||
let elapsed = format_duration(task.started_at.elapsed());
|
||||
let substatus_part = task
|
||||
.substatus
|
||||
.as_ref()
|
||||
.map(|s| truncate_with_ellipsis(s, 30))
|
||||
.unwrap_or_default();
|
||||
|
||||
let step_label = task.step.label();
|
||||
let step_color = task.step.color_code();
|
||||
let name_width = inner.max_example_name_len;
|
||||
|
||||
let prefix = format!(
|
||||
"{bold}{step_color}{step_label:>12}{reset} {name:<name_width$} {dim}│{reset} {substatus_part}",
|
||||
name = name,
|
||||
);
|
||||
|
||||
let duration_with_margin = format!("{elapsed} ");
|
||||
let padding_needed = inner
|
||||
.terminal_width
|
||||
.saturating_sub(RIGHT_MARGIN)
|
||||
.saturating_sub(duration_with_margin.len())
|
||||
.saturating_sub(strip_ansi_len(&prefix));
|
||||
let padding = " ".repeat(padding_needed);
|
||||
|
||||
eprintln!("{prefix}{padding}{dim}{duration_with_margin}{reset}");
|
||||
lines_printed += 1;
|
||||
}
|
||||
|
||||
inner.status_lines_displayed = lines_printed + 1; // +1 for the divider line
|
||||
let _ = std::io::stderr().flush();
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
Self::clear_status_lines(&mut inner);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StepProgress {
|
||||
progress: Arc<Progress>,
|
||||
step: Step,
|
||||
example_name: String,
|
||||
}
|
||||
|
||||
impl StepProgress {
|
||||
pub fn set_substatus(&self, substatus: impl Into<Cow<'static, str>>) {
|
||||
let mut inner = self.progress.inner.lock().unwrap();
|
||||
if let Some(task) = inner.in_progress.get_mut(&self.example_name) {
|
||||
task.substatus = Some(substatus.into().into_owned());
|
||||
Progress::clear_status_lines(&mut inner);
|
||||
Progress::print_status_lines(&mut inner);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_substatus(&self) {
|
||||
let mut inner = self.progress.inner.lock().unwrap();
|
||||
if let Some(task) = inner.in_progress.get_mut(&self.example_name) {
|
||||
task.substatus = None;
|
||||
Progress::clear_status_lines(&mut inner);
|
||||
Progress::print_status_lines(&mut inner);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_info(&self, info: impl Into<String>, style: InfoStyle) {
|
||||
let mut inner = self.progress.inner.lock().unwrap();
|
||||
if let Some(task) = inner.in_progress.get_mut(&self.example_name) {
|
||||
task.info = Some((info.into(), style));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StepProgress {
|
||||
fn drop(&mut self) {
|
||||
self.progress.finish(self.step, &self.example_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_terminal_width() -> usize {
|
||||
unsafe {
|
||||
let mut winsize: libc::winsize = std::mem::zeroed();
|
||||
if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ, &mut winsize) == 0
|
||||
&& winsize.ws_col > 0
|
||||
{
|
||||
winsize.ws_col as usize
|
||||
} else {
|
||||
80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn get_terminal_width() -> usize {
|
||||
80
|
||||
}
|
||||
|
||||
fn strip_ansi_len(s: &str) -> usize {
|
||||
let mut len = 0;
|
||||
let mut in_escape = false;
|
||||
for c in s.chars() {
|
||||
if c == '\x1b' {
|
||||
in_escape = true;
|
||||
} else if in_escape {
|
||||
if c == 'm' {
|
||||
in_escape = false;
|
||||
}
|
||||
} else {
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
fn truncate_with_ellipsis(s: &str, max_len: usize) -> String {
|
||||
if s.len() <= max_len {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{}…", &s[..max_len.saturating_sub(1)])
|
||||
}
|
||||
}
|
||||
|
||||
fn format_duration(duration: Duration) -> String {
|
||||
const MINUTE_IN_MILLIS: f32 = 60. * 1000.;
|
||||
|
||||
let millis = duration.as_millis() as f32;
|
||||
if millis < 1000.0 {
|
||||
format!("{}ms", millis)
|
||||
} else if millis < MINUTE_IN_MILLIS {
|
||||
format!("{:.1}s", millis / 1_000.0)
|
||||
} else {
|
||||
format!("{:.1}m", millis / MINUTE_IN_MILLIS)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
example::{Example, ExampleContext},
|
||||
headless::EpAppState,
|
||||
load_project::run_load_project,
|
||||
progress::{InfoStyle, Progress, Step, StepProgress},
|
||||
};
|
||||
use collections::HashSet;
|
||||
use edit_prediction::{DebugEvent, EditPredictionStore};
|
||||
@@ -9,18 +10,22 @@ use futures::{FutureExt as _, StreamExt as _, channel::mpsc};
|
||||
use gpui::{AsyncApp, Entity};
|
||||
use language::Buffer;
|
||||
use project::Project;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub async fn run_context_retrieval(
|
||||
example: &mut Example,
|
||||
app_state: Arc<EpAppState>,
|
||||
progress: Arc<Progress>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
if example.context.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
run_load_project(example, app_state.clone(), cx.clone()).await;
|
||||
run_load_project(example, app_state.clone(), progress.clone(), cx.clone()).await;
|
||||
|
||||
let step_progress = progress.start(Step::Context, &example.name);
|
||||
|
||||
let state = example.state.as_ref().unwrap();
|
||||
let project = state.project.clone();
|
||||
@@ -30,7 +35,7 @@ pub async fn run_context_retrieval(
|
||||
project.register_buffer_with_language_servers(&state.buffer, cx)
|
||||
})
|
||||
.unwrap();
|
||||
wait_for_language_servers_to_start(example, &project, &state.buffer, &mut cx).await;
|
||||
wait_for_language_servers_to_start(&project, &state.buffer, &step_progress, &mut cx).await;
|
||||
|
||||
let ep_store = cx
|
||||
.update(|cx| EditPredictionStore::try_global(cx).unwrap())
|
||||
@@ -58,19 +63,20 @@ pub async fn run_context_retrieval(
|
||||
.update(&mut cx, |store, cx| store.context_for_project(&project, cx))
|
||||
.unwrap();
|
||||
|
||||
let excerpt_count: usize = context_files.iter().map(|f| f.excerpts.len()).sum();
|
||||
step_progress.set_info(format!("{} excerpts", excerpt_count), InfoStyle::Normal);
|
||||
|
||||
example.context = Some(ExampleContext {
|
||||
files: context_files,
|
||||
});
|
||||
}
|
||||
|
||||
async fn wait_for_language_servers_to_start(
|
||||
example: &Example,
|
||||
project: &Entity<Project>,
|
||||
buffer: &Entity<Buffer>,
|
||||
step_progress: &Arc<StepProgress>,
|
||||
cx: &mut AsyncApp,
|
||||
) {
|
||||
let log_prefix = format!("{} | ", example.name);
|
||||
|
||||
let lsp_store = project
|
||||
.read_with(cx, |project, _| project.lsp_store())
|
||||
.unwrap();
|
||||
@@ -89,11 +95,7 @@ async fn wait_for_language_servers_to_start(
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
eprintln!(
|
||||
"{}⏵ Waiting for {} language servers",
|
||||
log_prefix,
|
||||
language_server_ids.len()
|
||||
);
|
||||
step_progress.set_substatus(format!("waiting for {} LSPs", language_server_ids.len()));
|
||||
|
||||
let timeout = cx
|
||||
.background_executor()
|
||||
@@ -102,10 +104,10 @@ async fn wait_for_language_servers_to_start(
|
||||
|
||||
let (mut tx, mut rx) = mpsc::channel(language_server_ids.len());
|
||||
let added_subscription = cx.subscribe(project, {
|
||||
let log_prefix = log_prefix.clone();
|
||||
let step_progress = step_progress.clone();
|
||||
move |_, event, _| match event {
|
||||
project::Event::LanguageServerAdded(language_server_id, name, _) => {
|
||||
eprintln!("{}+ Language server started: {}", log_prefix, name);
|
||||
step_progress.set_substatus(format!("LSP started: {}", name));
|
||||
tx.try_send(*language_server_id).ok();
|
||||
}
|
||||
_ => {}
|
||||
@@ -137,7 +139,7 @@ async fn wait_for_language_servers_to_start(
|
||||
let (mut tx, mut rx) = mpsc::channel(language_server_ids.len());
|
||||
let subscriptions = [
|
||||
cx.subscribe(&lsp_store, {
|
||||
let log_prefix = log_prefix.clone();
|
||||
let step_progress = step_progress.clone();
|
||||
move |_, event, _| {
|
||||
if let project::LspStoreEvent::LanguageServerUpdate {
|
||||
message:
|
||||
@@ -150,12 +152,12 @@ async fn wait_for_language_servers_to_start(
|
||||
..
|
||||
} = event
|
||||
{
|
||||
eprintln!("{}⟲ {message}", log_prefix)
|
||||
step_progress.set_substatus(message.clone());
|
||||
}
|
||||
}
|
||||
}),
|
||||
cx.subscribe(project, {
|
||||
let log_prefix = log_prefix.clone();
|
||||
let step_progress = step_progress.clone();
|
||||
move |_, event, cx| match event {
|
||||
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
|
||||
let lsp_store = lsp_store.read(cx);
|
||||
@@ -163,7 +165,7 @@ async fn wait_for_language_servers_to_start(
|
||||
.language_server_adapter_for_id(*language_server_id)
|
||||
.unwrap()
|
||||
.name();
|
||||
eprintln!("{}⚑ Language server idle: {}", log_prefix, name);
|
||||
step_progress.set_substatus(format!("LSP idle: {}", name));
|
||||
tx.try_send(*language_server_id).ok();
|
||||
}
|
||||
_ => {}
|
||||
@@ -192,4 +194,5 @@ async fn wait_for_language_servers_to_start(
|
||||
}
|
||||
|
||||
drop(subscriptions);
|
||||
step_progress.clear_substatus();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
headless::EpAppState,
|
||||
metrics::{self, ClassificationMetrics},
|
||||
predict::run_prediction,
|
||||
progress::{Progress, Step},
|
||||
};
|
||||
use edit_prediction::udiff::DiffLine;
|
||||
use gpui::AsyncApp;
|
||||
@@ -13,6 +14,7 @@ pub async fn run_scoring(
|
||||
example: &mut Example,
|
||||
args: &PredictArgs,
|
||||
app_state: Arc<EpAppState>,
|
||||
progress: Arc<Progress>,
|
||||
cx: AsyncApp,
|
||||
) {
|
||||
run_prediction(
|
||||
@@ -20,10 +22,13 @@ pub async fn run_scoring(
|
||||
Some(args.provider),
|
||||
args.repetitions,
|
||||
app_state,
|
||||
progress.clone(),
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let _progress = progress.start(Step::Score, &example.name);
|
||||
|
||||
let expected_patch = parse_patch(&example.expected_patch);
|
||||
|
||||
let mut scores = vec![];
|
||||
|
||||
Reference in New Issue
Block a user