on master help sign in
Detect workspace root using language markers (#1370)
8a019b4Alexis Mousset
helix-core/src/lib.rs+26−6
@@ -39,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
39 line.chars().position(|ch| !ch.is_whitespace())
39 line.chars().position(|ch| !ch.is_whitespace())
40}
40}
41
41
42−/// Find `.git` root.
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
43−pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
49+pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
44 let current_dir = std::env::current_dir().expect("unable to determine current directory");
50 let current_dir = std::env::current_dir().expect("unable to determine current directory");
45
51
46 let root = match root {
52 let root = match root {
@@ -52,16 +58,30 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
52 current_dir.join(root)
58 current_dir.join(root)
53 }
59 }
54 }
60 }
55− None => current_dir,
61+ None => current_dir.clone(),
56 };
62 };
57
63
64+ let mut top_marker = None;
58 for ancestor in root.ancestors() {
65 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
60 if ancestor.join(".git").is_dir() {
73 if ancestor.join(".git").is_dir() {
74+ // Use workspace if detected from marker
61− return Some(ancestor.to_path_buf());
75+ return Some(top_marker.unwrap_or(ancestor).to_path_buf());
62 }
76 }
63 }
77 }
64− None
78+
79+ // In absence of git repo, use workspace if detected
80+ if top_marker.is_some() {
81+ top_marker.map(|a| a.to_path_buf())
82+ } else {
83+ Some(current_dir)
84+ }
65}
85}
66
86
67pub fn runtime_dir() -> std::path::PathBuf {
87pub fn runtime_dir() -> std::path::PathBuf {
helix-lsp/src/client.rs+5−1
@@ -31,6 +31,7 @@ pub struct Client {
31 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
31 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
32 offset_encoding: OffsetEncoding,
32 offset_encoding: OffsetEncoding,
33 config: Option<Value>,
33 config: Option<Value>,
34+ root_markers: Vec<String>,
34}
35}
35
36
36impl Client {
37impl Client {
@@ -39,6 +40,7 @@ impl Client {
39 cmd: &str,
40 cmd: &str,
40 args: &[String],
41 args: &[String],
41 config: Option<Value>,
42 config: Option<Value>,
43+ root_markers: Vec<String>,
42 id: usize,
44 id: usize,
43 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
45 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
44 let process = Command::new(cmd)
46 let process = Command::new(cmd)
@@ -68,6 +70,7 @@ impl Client {
68 capabilities: OnceCell::new(),
70 capabilities: OnceCell::new(),
69 offset_encoding: OffsetEncoding::Utf8,
71 offset_encoding: OffsetEncoding::Utf8,
70 config,
72 config,
73+ root_markers,
71 };
74 };
72
75
73 Ok((client, server_rx, initialize_notify))
76 Ok((client, server_rx, initialize_notify))
@@ -225,7 +228,8 @@ impl Client {
225
228
226 pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
229 pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
227 // TODO: delay any requests that are triggered prior to initialize
230 // TODO: delay any requests that are triggered prior to initialize
231+ let root = find_root(None, &self.root_markers)
228− let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
232+ .and_then(|root| lsp::Url::from_file_path(root).ok());
229
233
230 if self.config.is_some() {
234 if self.config.is_some() {
231 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
235 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
helix-lsp/src/lib.rs+1
@@ -326,6 +326,7 @@ impl Registry {
326 &config.command,
326 &config.command,
327 &config.args,
327 &config.args,
328 language_config.config.clone(),
328 language_config.config.clone(),
329+ language_config.roots.clone(),
329 id,
330 id,
330 )?;
331 )?;
331 self.incoming.push(UnboundedReceiverStream::new(incoming));
332 self.incoming.push(UnboundedReceiverStream::new(incoming));
helix-term/src/commands.rs+2−1
@@ -3154,7 +3154,8 @@ fn command_mode(cx: &mut Context) {
3154}
3154}
3155
3155
3156fn file_picker(cx: &mut Context) {
3156fn file_picker(cx: &mut Context) {
3157+ // We don't specify language markers, root will be the root of the current git repo
3157− let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
3158+ let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
3158 let picker = ui::file_picker(root, &cx.editor.config);
3159 let picker = ui::file_picker(root, &cx.editor.config);
3159 cx.push_layer(Box::new(picker));
3160 cx.push_layer(Box::new(picker));
3160}
3161}
languages.toml+1−1
@@ -3,7 +3,7 @@ name = "rust"
3scope = "source.rust"
3scope = "source.rust"
4injection-regex = "rust"
4injection-regex = "rust"
5file-types = ["rs"]
5file-types = ["rs"]
6−roots = []
6+roots = ["Cargo.toml", "Cargo.lock"]
7auto-format = true
7auto-format = true
8comment-token = "//"
8comment-token = "//"
9language-server = { command = "rust-analyzer" }
9language-server = { command = "rust-analyzer" }