diff --git a/.editorconfig b/.editorconfig
index 995423a..14d6d9f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -11,3 +11,6 @@ trim_trailing_whitespace = false
[*.scm]
indent_size = 4
+
+[*.mjs]
+indent_size = 4
diff --git a/README.md b/README.md
index ee638e2..6fb1c97 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# Haxe language support for Zed
-> [!NOTE]
-> This extension is now published! Open the `Extensions` panel (`Ctrl+Shift+X`) and search for "Haxe".
+
## Usage
@@ -9,7 +8,11 @@ The syntax highlighting should appear immediately.
The backing language server should be downloaded automatically in the background.
-To make the LSP use a specific `.hxml` configuration, create a `.zed/settings.json` file in your project root:
+This extension detects all `.hxml` files in the workspace root,
+and then tries to choose the best one as configuration.
+This decision might be wrong!
+To explicitly use a specific `.hxml` file,
+create a `.zed/settings.json` file in your project root:
```json
{
@@ -26,7 +29,7 @@ To make the LSP use a specific `.hxml` configuration, create a `.zed/settings.js
Additional settings you may want to pass to the language server: (non-exhaustive)
- [(reference)](https://github.com/vshaxe/haxe-language-server/blob/9c3114de15bfd8096833ee50aab131459347e3f7/src/haxeLanguageServer/Configuration.hx#L134)
+ [(reference)](https://github.com/vshaxe/haxe-language-server/blob/65ba91ce13e413fe721d371cdf9e39024a53f2ec/src/haxeLanguageServer/Configuration.hx#L136)
```json
{
@@ -48,8 +51,43 @@ To make the LSP use a specific `.hxml` configuration, create a `.zed/settings.js
}
}
```
+
+## Usage (Lime/OpenFL/HaxeFlixel)
+
+The Haxe language server does not natively understand Lime projects,
+supporting `.hxml` files only.
+However, Lime lets you easily generate them.
+
+Assuming that
+
+- you have a `Project.xml` file in your workspace root, and
+- you're targetting `html5`,
+
+you can run in your favorite shell:
+
+```sh
+lime build html5
+lime display html5 > html5.hxml
+```
+
+This creates a `html5.hxml` file telling Haxe which libraries to include
+and what platform to target.
+See [the instructions above](#usage) on how to tell Zed to use this file.
+
+## Usage (other)
+
+Many other Haxe toolchains can create `.hxml` files, including:
+
+```sh
+nme prepare html5
+```
+
+```sh
+ceramic clay hxml web > web.hxml
+```
+
## Install nightly
To use dev Zed extensions, you will need to have [Rust compiler installed](https://rustup.rs/).
diff --git a/extension.toml b/extension.toml
index 3351f57..05b10fc 100644
--- a/extension.toml
+++ b/extension.toml
@@ -17,3 +17,8 @@ commit = "6199e87f50afedab695de1c835430da424ed4ca9"
[language_servers.haxe-language-server]
name = "Haxe Language Server"
languages = ["Haxe", "HXML"]
+
+[[capabilities]]
+kind = "process:exec"
+command = "*"
+args = ["-e", "*", "--", "**"]
diff --git a/media/example-gruvbox.webp b/media/example-gruvbox.webp
new file mode 100644
index 0000000..7652827
Binary files /dev/null and b/media/example-gruvbox.webp differ
diff --git a/src/extension.rs b/src/extension.rs
index 7e265b2..dbbf10f 100644
--- a/src/extension.rs
+++ b/src/extension.rs
@@ -10,7 +10,10 @@ use zed_extension_api::{
Result, Worktree,
};
-use crate::language_server::{self};
+use crate::{
+ helper_scripts,
+ language_server::{self},
+};
pub struct HaxeExtension {
language_server_binary_path: Option,
@@ -65,7 +68,7 @@ impl zed::Extension for HaxeExtension {
// Try to fetch the latest version of the language server and download it
let version = match language_server::download_from_gh_if_missing(self, Some(id)) {
Ok(version) => {
- let last_version_path = self.working_dir.join("version.txt");
+ let last_version_path = (&self.working_dir).join("version.txt");
std::fs::write(last_version_path, &version).ok();
self.language_server_version = Some(version.clone());
version
@@ -120,39 +123,83 @@ impl zed::Extension for HaxeExtension {
Value::Null => {
// Ideally, the language server has *some* .hxml config file present.
// The problem is, Zed does not provide a way to list files in the worktree.
- // This makes it impossible to find the correct .hxml file to use as config.
-
- // However, we can check for existence of files we know exact names of,
- // which can tell us that the current setup is completely wrong:
+ // We need to delegate to a Node script:
+ let mut command = zed::Command {
+ command: zed::node_binary_path()?,
+ args: vec![
+ "-e".to_string(), // Eval mode
+ helper_scripts::DETECT_PROJECT_FILES.to_string(), // Inline the script
+ "--".to_string(), // Custom args
+ trim_leading_slash_on_windows(worktree.root_path()), // Where to look
+ ],
+ env: vec![],
+ };
- if worktree.read_text_file("Project.xml").is_ok() {
- return Err(concat!(
- "Lime/OpenFL projects are not fully supported at this time!\n\n",
- "You should generate a .hxml file using `lime display html5 > html5.hxml`\n",
- "and consult the documentation for how to pass it to the language server:\n",
- ""
+ match command.output() {
+ Ok(output) => {
+ let status_code = output.status.unwrap_or(0);
+ let file_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ match status_code {
+ 0 => {
+ // As a fallback, use our (almost) blank, default config
+ // we created while our extension was loading:
+ *display_args = json!([trim_leading_slash_on_windows(
+ self.working_dir()
+ .join("default-config.hxml")
+ .to_string_lossy()
+ .to_string()
+ )]);
+ }
+ 101 => {
+ // HXML file found
+ *display_args = json!([file_path]);
+ }
+ 102 => {
+ // Lime's XML file found
+ return Err(concat!(
+"Lime/OpenFL projects are not fully supported at this time!\n\n",
+"You should generate a .hxml file using `lime display html5 > html5.hxml`\n",
+"and consult the documentation for how to pass it to the language server:\n",
+""
)
- .into());
- }
-
- if worktree.read_text_file("ceramic.yml").is_ok() {
- return Err(concat!(
- "Ceramic projects are not fully supported at this time!\n\n",
- "You should generate a .hxml file using `ceramic clay hxml web > web.hxml`\n",
- "and consult the documentation for how to pass it to the language server:\n",
- ""
+ .to_string());
+ }
+ 103 => {
+ // Ceramic's YML file found
+ return Err(concat!(
+"Ceramic projects are not fully supported at this time!\n\n",
+"You should generate a .hxml file using `ceramic clay hxml web > web.hxml`\n",
+"and consult the documentation for how to pass it to the language server:\n",
+""
+ )
+ .to_string());
+ }
+ 104 => {
+ // NME's NMML file found
+ return Err(concat!(
+"NME projects are not fully supported at this time!\n\n",
+"You should run `nme prepare`, which generates a `bin/[platform]/haxe/build.hxml` file\n",
+"and consult the documentation for how to pass it to the language server:\n",
+""
)
- .into());
+ .to_string());
+ }
+ _ => {
+ return Err(format!(
+ "Internal error in discover script (return code {}):\n\nstderr:\n{}\nstdout:\n{}",
+ status_code,
+ String::from_utf8_lossy(&output.stderr),
+ String::from_utf8_lossy(&output.stdout)
+ ));
+ }
+ }
+ }
+ Err(e) => {
+ return Err(format!(
+ "Could not run the project discover script:\n\n{e:?}"
+ ));
+ }
}
-
- // As a fallback, use our (almost) blank, default config
- // we created while our extension was loading:
- *display_args = json!([trim_leading_slash_on_windows(
- self.working_dir()
- .join("default-config.hxml")
- .to_string_lossy()
- .to_string()
- )]);
}
_ => {}
}
diff --git a/src/helper_scripts/detect-project-files.mjs b/src/helper_scripts/detect-project-files.mjs
new file mode 100644
index 0000000..910f9aa
--- /dev/null
+++ b/src/helper_scripts/detect-project-files.mjs
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: MIT
+import { readdir } from "node:fs/promises";
+import process, { argv, chdir, stderr, stdout } from "node:process";
+
+const workspacePath = argv[1]; // Node strips everything before "--"
+chdir(workspacePath);
+
+const SCORES = [
+ { sub: "include", delta: -50 },
+ { sub: "test", delta: -10 },
+ { sub: "debug", delta: -10 },
+ { sub: "sample", delta: -5 },
+ { sub: "build", delta: 30 },
+ { sub: "compile", delta: 30 },
+ { sub: "main", delta: 50 },
+ { sub: "lsp", delta: 100 },
+ { sub: "devenv", delta: 100 },
+];
+
+const entries = await readdir(workspacePath, { withFileTypes: true });
+const files = entries.filter((e) => e.isFile()).map((e) => e.name);
+
+const hxmlPaths = [];
+for (const relativePath of files.filter((p) =>
+ p.toLowerCase().endsWith(".hxml")
+)) {
+ let score = 0;
+ const pathLowerCase = relativePath.toLowerCase();
+ if (pathLowerCase === "extraparams.hxml") {
+ continue;
+ }
+ for (const { sub, delta } of SCORES) {
+ if (pathLowerCase.includes(sub)) {
+ score += delta;
+ }
+ }
+ hxmlPaths.push({ relativePath, score });
+}
+
+// Calling `process.exit` is discouraged as it may not flush stdout writes
+process.exitCode = (() => {
+ hxmlPaths.sort((a, b) => b.score - a.score);
+ if (hxmlPaths.length >= 1) {
+ stdout.write(hxmlPaths[0].relativePath, "utf8");
+ return 101;
+ }
+
+ const projectXml = files.find((p) => p.toLowerCase() === "project.xml");
+ if (projectXml) {
+ stdout.write(projectXml, "utf8");
+ return 102;
+ }
+
+ const ceramicYml = files.find((p) => p.toLowerCase() === "ceramic.yml");
+ if (ceramicYml) {
+ stdout.write(ceramicYml, "utf8");
+ return 103;
+ }
+
+ const nmml = files.find((p) => p.toLowerCase().endsWith(".nmml"));
+ if (nmml) {
+ stdout.write(nmml, "utf8");
+ return 104;
+ }
+
+ return 0;
+})();
+
+stderr.end();
+stdout.end();
diff --git a/src/helper_scripts/mod.rs b/src/helper_scripts/mod.rs
new file mode 100644
index 0000000..1b64ac0
--- /dev/null
+++ b/src/helper_scripts/mod.rs
@@ -0,0 +1 @@
+pub const DETECT_PROJECT_FILES: &'static str = include_str!("detect-project-files.mjs");
diff --git a/src/lib.rs b/src/lib.rs
index b0370c2..f5b9e4c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
use zed_extension_api::{self as zed};
mod extension;
+mod helper_scripts;
mod language_server;
zed::register_extension!(crate::extension::HaxeExtension);