Compare commits

...

6 Commits

Author SHA1 Message Date
Peter Tripp
73f7061b1d Build on every run. 1.9s 2025-06-21 16:52:19 -04:00
Peter Tripp
332660d888 Serious vibes 2025-06-21 16:42:02 -04:00
Peter Tripp
a85915486b Only do this in test, not in release 2025-06-21 15:57:01 -04:00
Peter Tripp
1b73741d6d Build with build.rs 2025-06-21 15:56:33 -04:00
Peter Tripp
b595445e6a Include actions.json 2025-06-21 15:39:30 -04:00
Peter Tripp
33e4300647 add dump 2025-06-21 15:37:06 -04:00
9 changed files with 3985 additions and 29 deletions

19
Cargo.lock generated
View File

@@ -2,6 +2,20 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "actions"
version = "0.1.0"
dependencies = [
"anyhow",
"command_palette",
"gpui",
"serde",
"serde_json",
"util",
"workspace-hack",
"zed",
]
[[package]]
name = "activity_indicator"
version = "0.1.0"
@@ -4596,18 +4610,17 @@ dependencies = [
name = "docs_preprocessor"
version = "0.1.0"
dependencies = [
"actions",
"anyhow",
"clap",
"command_palette",
"gpui",
"mdbook",
"regex",
"rust-embed",
"serde",
"serde_json",
"settings",
"util",
"workspace-hack",
"zed",
]
[[package]]

View File

@@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"crates/actions",
"crates/activity_indicator",
"crates/agent",
"crates/agent_settings",
@@ -200,7 +201,7 @@ members = [
"tooling/workspace-hack",
"tooling/xtask",
]
default-members = ["crates/zed"]
default-members = ["crates/zed", "crates/actions"]
[workspace.package]
publish = false
@@ -212,6 +213,7 @@ edition = "2024"
# Workspace member crates
#
actions = { path = "crates/actions" }
activity_indicator = { path = "crates/activity_indicator" }
agent = { path = "crates/agent" }
agent_settings = { path = "crates/agent_settings" }

3774
assets/actions/actions.json Normal file

File diff suppressed because it is too large Load Diff

28
crates/actions/Cargo.toml Normal file
View File

@@ -0,0 +1,28 @@
[package]
name = "actions"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[dependencies]
anyhow.workspace = true
command_palette.workspace = true
gpui.workspace = true
serde.workspace = true
serde_json.workspace = true
util.workspace = true
workspace-hack.workspace = true
zed.workspace = true
[build-dependencies]
anyhow.workspace = true
command_palette.workspace = true
gpui.workspace = true
serde.workspace = true
serde_json.workspace = true
util.workspace = true
zed.workspace = true
[lints]
workspace = true

59
crates/actions/build.rs Normal file
View File

@@ -0,0 +1,59 @@
use anyhow::Result;
use serde::Serialize;
use std::env;
use std::fs;
use std::path::Path;
#[derive(Debug, Serialize)]
struct ActionDef {
name: String,
human_name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
deprecated_aliases: Vec<String>,
}
fn main() -> Result<()> {
// call a zed:: function so everything in `zed` crate is linked and
// all actions in the actual app are registered
zed::stdout_is_a_pty();
let actions = dump_all_gpui_actions();
let out_dir = env::var("CARGO_MANIFEST_DIR")?;
let assets_path = Path::new(&out_dir)
.parent()
.unwrap()
.parent()
.unwrap()
.join("assets/actions");
// Create the actions directory if it doesn't exist
fs::create_dir_all(&assets_path)?;
let json_path = assets_path.join("actions.json");
let json_content = serde_json::to_string_pretty(&actions)?;
fs::write(&json_path, json_content)?;
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../../assets/actions/actions.json");
// This sucks. We regenerate actions.json on every cargo invocation
println!("cargo:rerun-if-changed=*");
Ok(())
}
fn dump_all_gpui_actions() -> Vec<ActionDef> {
let mut actions = gpui::generate_list_of_all_registered_actions()
.into_iter()
.map(|action| ActionDef {
name: action.name.to_string(),
human_name: command_palette::humanize_action_name(action.name),
deprecated_aliases: action.aliases.iter().map(|s| s.to_string()).collect(),
})
.collect::<Vec<ActionDef>>();
actions.sort_by_key(|a| a.name.clone());
actions
}

18
crates/actions/src/lib.rs Normal file
View File

@@ -0,0 +1,18 @@
//! Actions crate that generates actions.json at build time.
//!
//! This crate uses build.rs to automatically regenerate the actions.json file
//! whenever the crate is built, eliminating the need for manual updates.
use serde::{Deserialize, Serialize};
/// Represents an action definition that can be serialized to/from JSON.
/// This struct is shared between the build script that generates actions.json
/// and other crates that need to read action definitions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionDef {
pub name: String,
pub human_name: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub deprecated_aliases: Vec<String>,
}

View File

@@ -6,6 +6,7 @@ publish.workspace = true
license = "GPL-3.0-or-later"
[dependencies]
actions.workspace = true
anyhow.workspace = true
clap.workspace = true
mdbook = "0.4.40"
@@ -13,11 +14,9 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
regex.workspace = true
rust-embed.workspace = true
util.workspace = true
workspace-hack.workspace = true
zed.workspace = true
gpui.workspace = true
command_palette.workspace = true
[lints]
workspace = true

View File

@@ -1,15 +1,22 @@
use actions::ActionDef;
use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use mdbook::BookItem;
use mdbook::book::{Book, Chapter};
use mdbook::preprocess::CmdPreprocessor;
use regex::Regex;
use rust_embed::RustEmbed;
use settings::KeymapFile;
use std::collections::HashSet;
use std::io::{self, Read};
use std::process;
use std::sync::LazyLock;
#[derive(RustEmbed)]
#[folder = "../../assets"]
#[include = "actions/*"]
struct Assets;
static KEYMAP_MACOS: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-macos.json").expect("Failed to load MacOS keymap")
});
@@ -18,7 +25,7 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
});
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(load_all_actions);
pub fn make_app() -> Command {
Command::new("zed-docs-preprocessor")
@@ -32,9 +39,6 @@ pub fn make_app() -> Command {
fn main() -> Result<()> {
let matches = make_app().get_matches();
// call a zed:: function so everything in `zed` crate is linked and
// all actions in the actual app are registered
zed::stdout_is_a_pty();
if let Some(sub_args) = matches.subcommand_matches("supports") {
handle_supports(sub_args);
@@ -54,7 +58,7 @@ enum Error {
impl Error {
fn new_for_not_found_action(action_name: String) -> Self {
for action in &*ALL_ACTIONS {
for alias in action.deprecated_aliases {
for alias in &action.deprecated_aliases {
if alias == &action_name {
return Error::DeprecatedActionUsed {
used: action_name.clone(),
@@ -163,7 +167,7 @@ fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<Error>) {
fn find_action_by_name(name: &str) -> Option<&ActionDef> {
ALL_ACTIONS
.binary_search_by(|action| action.name.cmp(name))
.binary_search_by(|action| action.name.as_str().cmp(name))
.ok()
.map(|index| &ALL_ACTIONS[index])
}
@@ -234,24 +238,80 @@ where
});
}
#[derive(Debug, serde::Serialize)]
struct ActionDef {
name: &'static str,
human_name: String,
deprecated_aliases: &'static [&'static str],
fn load_all_actions() -> Vec<ActionDef> {
let content = util::asset_str::<Assets>("actions/actions.json");
let mut actions: Vec<ActionDef> =
serde_json::from_str(content.as_ref()).expect("Failed to parse actions.json");
actions.sort_by(|a, b| a.name.cmp(&b.name));
actions
}
fn dump_all_gpui_actions() -> Vec<ActionDef> {
let mut actions = gpui::generate_list_of_all_registered_actions()
.into_iter()
.map(|action| ActionDef {
name: action.name,
human_name: command_palette::humanize_action_name(action.name),
deprecated_aliases: action.aliases,
})
.collect::<Vec<ActionDef>>();
#[cfg(test)]
mod tests {
use super::*;
actions.sort_by_key(|a| a.name);
#[test]
fn test_load_actions() {
let actions = load_all_actions();
assert!(!actions.is_empty(), "Actions should not be empty");
return actions;
// Check that actions are sorted
for i in 1..actions.len() {
assert!(
actions[i - 1].name <= actions[i].name,
"Actions should be sorted by name"
);
}
// Check that we can find a common action
assert!(
find_action_by_name("editor::Cut").is_some(),
"Should be able to find editor::Cut action"
);
}
#[test]
fn test_find_action_by_name() {
// Test finding an action that exists
let action = find_action_by_name("editor::Cut");
assert!(action.is_some());
assert_eq!(action.unwrap().name, "editor::Cut");
// Test finding an action that doesn't exist
let action = find_action_by_name("nonexistent::Action");
assert!(action.is_none());
}
#[test]
fn test_name_for_action() {
// Test simple action name
assert_eq!(name_for_action("editor::Cut".to_string()), "editor::Cut");
// Test action with parameters
assert_eq!(
name_for_action(
"\"editor::ToggleComments\", {\"advance_downwards\":false}".to_string()
),
"editor::ToggleComments"
);
// Test action with quotes
assert_eq!(
name_for_action("\"workspace::NewFile\"".to_string()),
"workspace::NewFile"
);
}
#[test]
fn test_error_creation() {
// Test creating error for non-existent action
let error = Error::new_for_not_found_action("nonexistent::Action".to_string());
match error {
Error::ActionNotFound { action_name } => {
assert_eq!(action_name, "nonexistent::Action");
}
_ => panic!("Expected ActionNotFound error"),
}
}
}

View File

@@ -158,6 +158,9 @@ windows.workspace = true
[target.'cfg(target_os = "windows")'.build-dependencies]
winresource = "0.1"
[build-dependencies]
zed_actions.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
ashpd.workspace = true