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