Skip to content

Commit 026e839

Browse files
committed
Detect workspace root using language markers
1 parent 6af0d51 commit 026e839

File tree

5 files changed

+39
-9
lines changed

5 files changed

+39
-9
lines changed

helix-core/src/lib.rs

+30-6
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
3939
line.chars().position(|ch| !ch.is_whitespace())
4040
}
4141

42-
/// Find `.git` root.
43-
pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
42+
/// Find project root.
43+
///
44+
/// Order of detection:
45+
/// * Top-most folder containing a root marker in current git repository
46+
/// * Git repostory root if no marker detected
47+
/// * Top-most folder containing a root marker if not git repository detected
48+
/// * Current working directory as fallback
49+
pub fn find_root(root: Option<&str>, root_markers: Vec<String>) -> Option<std::path::PathBuf> {
4450
let current_dir = std::env::current_dir().expect("unable to determine current directory");
4551

4652
let root = match root {
@@ -52,16 +58,34 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
5258
current_dir.join(root)
5359
}
5460
}
55-
None => current_dir,
61+
None => current_dir.clone(),
5662
};
5763

64+
let mut top_marker = None;
5865
for ancestor in root.ancestors() {
59-
// TODO: also use defined roots if git isn't found
66+
for marker in &root_markers {
67+
if ancestor.join(marker).exists() {
68+
top_marker = Some(ancestor);
69+
break;
70+
}
71+
}
72+
// don't go higher than repo
6073
if ancestor.join(".git").is_dir() {
61-
return Some(ancestor.to_path_buf());
74+
// Use workspace if detected from marker
75+
if top_marker.is_some() {
76+
return top_marker.map(|a| a.to_path_buf());
77+
} else {
78+
return Some(ancestor.to_path_buf());
79+
}
6280
}
6381
}
64-
None
82+
83+
// In absence of git repo, use workspace if detected
84+
if top_marker.is_some() {
85+
top_marker.map(|a| a.to_path_buf())
86+
} else {
87+
Some(current_dir)
88+
}
6589
}
6690

6791
pub fn runtime_dir() -> std::path::PathBuf {

helix-lsp/src/client.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct Client {
3131
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
3232
offset_encoding: OffsetEncoding,
3333
config: Option<Value>,
34+
root_markers: Vec<String>,
3435
}
3536

3637
impl Client {
@@ -39,6 +40,7 @@ impl Client {
3940
cmd: &str,
4041
args: &[String],
4142
config: Option<Value>,
43+
root_markers: Vec<String>,
4244
id: usize,
4345
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
4446
let process = Command::new(cmd)
@@ -68,6 +70,7 @@ impl Client {
6870
capabilities: OnceCell::new(),
6971
offset_encoding: OffsetEncoding::Utf8,
7072
config,
73+
root_markers,
7174
};
7275

7376
Ok((client, server_rx, initialize_notify))
@@ -225,7 +228,8 @@ impl Client {
225228

226229
pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
227230
// TODO: delay any requests that are triggered prior to initialize
228-
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
231+
let root = find_root(None, self.root_markers.clone())
232+
.and_then(|root| lsp::Url::from_file_path(root).ok());
229233

230234
if self.config.is_some() {
231235
log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());

helix-lsp/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ impl Registry {
326326
&config.command,
327327
&config.args,
328328
language_config.config.clone(),
329+
language_config.roots.clone(),
329330
id,
330331
)?;
331332
self.incoming.push(UnboundedReceiverStream::new(incoming));

helix-term/src/commands.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3026,7 +3026,8 @@ fn command_mode(cx: &mut Context) {
30263026
}
30273027

30283028
fn file_picker(cx: &mut Context) {
3029-
let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
3029+
// We don't specify language markers, root will be the root of the current git repo
3030+
let root = find_root(None, vec![]).unwrap_or_else(|| PathBuf::from("./"));
30303031
let picker = ui::file_picker(root, &cx.editor.config);
30313032
cx.push_layer(Box::new(picker));
30323033
}

languages.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "rust"
33
scope = "source.rust"
44
injection-regex = "rust"
55
file-types = ["rs"]
6-
roots = []
6+
roots = ["Cargo.toml", "Cargo.lock"]
77
auto-format = true
88
comment-token = "//"
99
language-server = { command = "rust-analyzer" }

0 commit comments

Comments
 (0)