mirror of https://github.com/helix-editor/helix
Add code actions on save
* Add code-actions-on-save config * Match VS Code config to allow future flexibility * Refactor lsp commands to allow for code reuse * Attempt code actions for all language servers for the document * Add lsp specific integration tests * Update documentation in book * Canonicalize path argument when retrieving documents by path * Resolves issue when running lsp integration tests in windowspull/6486/head
parent
1b5295a3f3
commit
af5a16f6e3
@ -0,0 +1,18 @@
|
||||
#[cfg(feature = "integration-lsp")]
|
||||
mod test {
|
||||
mod helpers;
|
||||
|
||||
use helix_term::config::Config;
|
||||
|
||||
use indoc::indoc;
|
||||
|
||||
use self::helpers::*;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn hello_world() -> anyhow::Result<()> {
|
||||
test(("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#")).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod lsp;
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
use helix_term::application::Application;
|
||||
use std::{
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
// Check that we have gopls available while also allowing
|
||||
// for gopls to initialize
|
||||
fn assert_gopls(app: &Application, path: &Path) {
|
||||
let doc = app.editor.document_by_path(path);
|
||||
let mut ls = None;
|
||||
let mut initialized = false;
|
||||
if let Some(doc) = doc {
|
||||
for _ in 0..10 {
|
||||
ls = doc.language_servers().find(|s| s.name() == "gopls");
|
||||
|
||||
if let Some(gopls) = ls {
|
||||
if gopls.is_initialized() {
|
||||
initialized = true;
|
||||
// TODO: Make this deterministic
|
||||
// Sleep to give time to send textDocument/didOpen notification
|
||||
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
doc.is_some(),
|
||||
"doc not found {:?} in {:?}",
|
||||
path,
|
||||
app.editor
|
||||
.documents
|
||||
.iter()
|
||||
.filter_map(|(_, d)| d.path())
|
||||
.collect::<Vec<&PathBuf>>()
|
||||
);
|
||||
assert!(ls.is_some(), "gopls language server not found");
|
||||
assert!(initialized, "gopls language server not initialized in time");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_organize_imports_go() -> anyhow::Result<()> {
|
||||
let lang_conf = indoc! {r#"
|
||||
[[language]]
|
||||
name = "go"
|
||||
code-actions-on-save = [{ code-action = "source.organizeImports", enabled = true }]
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
"#};
|
||||
|
||||
let text = indoc! {r#"
|
||||
#[p|]#ackage main
|
||||
|
||||
import "fmt"
|
||||
|
||||
import "path"
|
||||
|
||||
func main() {
|
||||
fmt.Println("a")
|
||||
path.Join("b")
|
||||
}
|
||||
"#};
|
||||
|
||||
let dir = tempfile::Builder::new().tempdir()?;
|
||||
let mut file = tempfile::Builder::new().suffix(".go").tempfile_in(&dir)?;
|
||||
let mut app = helpers::AppBuilder::new()
|
||||
.with_config(Config::default())
|
||||
.with_lang_loader(helpers::test_syntax_loader(Some(lang_conf.into())))
|
||||
.with_file(file.path(), None)
|
||||
.with_input_text(text)
|
||||
.build()?;
|
||||
|
||||
test_key_sequences(
|
||||
&mut app,
|
||||
vec![
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
assert_gopls(app, file.path());
|
||||
}),
|
||||
),
|
||||
(Some(":w<ret>"), None),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_file_has_content(
|
||||
&mut file,
|
||||
"package main\n\nimport (\n\t\"fmt\"\n\t\"path\"\n)\n\nfunc main() {\n\tfmt.Println(\"a\")\n\tpath.Join(\"b\")\n}\n"
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_organize_imports_go_write_all_quit() -> anyhow::Result<()> {
|
||||
let lang_conf = indoc! {r#"
|
||||
[[language]]
|
||||
name = "go"
|
||||
code-actions-on-save = [{ code-action = "source.organizeImports", enabled = true }]
|
||||
"#};
|
||||
|
||||
let text = indoc! {r#"
|
||||
#[p|]#ackage main
|
||||
|
||||
import "path"
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("a")
|
||||
path.Join("b")
|
||||
}
|
||||
"#};
|
||||
|
||||
let dir = tempfile::Builder::new().tempdir()?;
|
||||
let mut file1 = tempfile::Builder::new().suffix(".go").tempfile_in(&dir)?;
|
||||
let mut file2 = tempfile::Builder::new().suffix(".go").tempfile_in(&dir)?;
|
||||
let mut app = helpers::AppBuilder::new()
|
||||
.with_config(Config::default())
|
||||
.with_lang_loader(helpers::test_syntax_loader(Some(lang_conf.into())))
|
||||
.with_file(file1.path(), None)
|
||||
.with_input_text(text)
|
||||
.build()?;
|
||||
|
||||
test_key_sequences(
|
||||
&mut app,
|
||||
vec![
|
||||
(
|
||||
Some(&format!(
|
||||
":o {}<ret>ipackage main<ret>import \"fmt\"<ret>func test()<ret><esc>",
|
||||
file2.path().to_string_lossy(),
|
||||
)),
|
||||
None,
|
||||
),
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
assert_gopls(app, file1.path());
|
||||
assert_gopls(app, file2.path());
|
||||
}),
|
||||
),
|
||||
(Some(":wqa<ret>"), None),
|
||||
],
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert_file_has_content(
|
||||
&mut file1,
|
||||
"package main\n\nimport (\n\t\"fmt\"\n\t\"path\"\n)\n\nfunc main() {\n\tfmt.Println(\"a\")\n\tpath.Join(\"b\")\n}\n",
|
||||
)?;
|
||||
|
||||
assert_file_has_content(&mut file2, "package main\n\nfunc test()\n")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_invalid_code_action_go() -> anyhow::Result<()> {
|
||||
let lang_conf = indoc! {r#"
|
||||
[[language]]
|
||||
name = "go"
|
||||
code-actions-on-save = [{ code-action = "source.invalid", enabled = true }]
|
||||
"#};
|
||||
|
||||
let text = indoc! {r#"
|
||||
#[p|]#ackage main
|
||||
|
||||
import "fmt"
|
||||
|
||||
import "path"
|
||||
|
||||
func main() {
|
||||
fmt.Println("a")
|
||||
path.Join("b")
|
||||
}
|
||||
"#};
|
||||
|
||||
let dir = tempfile::Builder::new().tempdir()?;
|
||||
let mut file = tempfile::Builder::new().suffix(".go").tempfile_in(&dir)?;
|
||||
let mut app = helpers::AppBuilder::new()
|
||||
.with_config(Config::default())
|
||||
.with_lang_loader(helpers::test_syntax_loader(Some(lang_conf.into())))
|
||||
.with_file(file.path(), None)
|
||||
.with_input_text(text)
|
||||
.build()?;
|
||||
|
||||
test_key_sequences(
|
||||
&mut app,
|
||||
vec![
|
||||
(
|
||||
None,
|
||||
Some(&|app| {
|
||||
assert_gopls(app, file.path());
|
||||
}),
|
||||
),
|
||||
(
|
||||
Some(":w<ret>"),
|
||||
Some(&|app| {
|
||||
assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status());
|
||||
}),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
reload_file(&mut file).unwrap();
|
||||
let mut file_content = String::new();
|
||||
file.as_file_mut().read_to_string(&mut file_content)?;
|
||||
|
||||
assert_file_has_content(
|
||||
&mut file,
|
||||
"package main\n\nimport \"fmt\"\n\nimport \"path\"\n\nfunc main() {\n\tfmt.Println(\"a\")\n\tpath.Join(\"b\")\n}\n",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
use super::*;
|
||||
|
||||
mod code_actions_on_save;
|
Loading…
Reference in New Issue