1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! The seam between refactoring tools and the engines that compute edits.
//!
//! A backend answers with a WorkspaceEdit-shaped [`Changeset`]; the tool
//! lowers it through the shared [`super::refactor_core::plan_file`] and
//! [`super::refactor_core::write_plans`] path, so dry runs, staged sessions,
//! previews and undo snapshots do not depend on which backend ran.
use harn_vm::VmValue;
use crate::error::HostlibError;
use crate::host_call::Interrupt;
use crate::tools::args::{build_dict, str_value};
use super::refactor_core::EditSpan;
/// One file's edits against the bytes the backend was given.
pub(super) struct FileEdits {
/// Index-relative path.
pub path: String,
pub source: String,
pub edits: Vec<EditSpan>,
}
pub(super) type Changeset = Vec<FileEdits>;
pub(super) enum BackendOutcome {
Changeset(Changeset),
/// The backend understood the request and refused it on semantic grounds;
/// trying a less precise engine would be wrong.
Declined(String),
/// The backend could not answer; the next backend may.
Unavailable(String),
/// The caller stopped the call; no backend may run after it.
Interrupted(Interrupt),
}
/// Why a backend step gave no answer.
pub(super) enum Unanswered {
/// The backend could not answer; the next backend may.
Unavailable(String),
/// The caller stopped the call; no backend may run after it.
Interrupted(Interrupt),
}
/// Which engines a request may use. `HARN_REFACTOR_BACKEND` sets the
/// process default; the request's `backend` field overrides it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum BackendChoice {
/// The language server, falling back to the syntax-tree engine.
Auto,
/// The language server only.
LanguageServer,
/// The syntax-tree engine only.
SyntaxTree,
}
impl BackendChoice {
pub(super) fn parse(builtin: &'static str, raw: &str) -> Result<Self, HostlibError> {
match raw {
"auto" => Ok(Self::Auto),
"language_server" => Ok(Self::LanguageServer),
"syntax_tree" => Ok(Self::SyntaxTree),
other => Err(HostlibError::InvalidParameter {
builtin,
param: "backend",
message: format!(
"expected `auto`, `language_server` or `syntax_tree`, got `{other}`"
),
}),
}
}
pub(super) fn resolve(
builtin: &'static str,
requested: Option<&str>,
) -> Result<Self, HostlibError> {
match requested {
Some(raw) => Self::parse(builtin, raw),
None => match std::env::var("HARN_REFACTOR_BACKEND") {
Ok(raw) if !raw.is_empty() => Self::parse(builtin, &raw),
_ => Ok(Self::Auto),
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum ServerStart {
/// Started by this call.
Cold,
/// Reused from an earlier call in this process.
Warm,
}
/// Which engine produced a result, reported on every response so evals can
/// measure precision and fallback rate.
#[derive(Clone, Debug, Default)]
pub(super) struct BackendReport {
pub language_server: Option<ServerRun>,
/// Set when the syntax-tree engine ran because the server could not.
pub fallback_reason: Option<String>,
/// True when the syntax-tree engine produced the result.
pub syntax_tree: bool,
}
#[derive(Clone, Debug)]
pub(super) struct ServerRun {
pub server: &'static str,
pub start: ServerStart,
pub elapsed_ms: u64,
}
impl BackendReport {
pub(super) fn value(&self) -> VmValue {
let name = if self.syntax_tree {
"syntax_tree"
} else if self.language_server.is_some() {
"language_server"
} else {
"none"
};
let mut entries = vec![("name", str_value(name))];
if let Some(run) = &self.language_server {
entries.push(("server", str_value(run.server)));
entries.push((
"server_start",
str_value(match run.start {
ServerStart::Cold => "cold",
ServerStart::Warm => "warm",
}),
));
entries.push(("server_elapsed_ms", VmValue::Int(run.elapsed_ms as i64)));
}
if let Some(reason) = &self.fallback_reason {
entries.push(("fallback_reason", str_value(reason)));
}
build_dict(entries)
}
}