Remove zed dependency from docs_preprocessor (#45130)

Closes #ISSUE

Uses the existing `--dump-all-actions` arg on the Zed binary to generate
an asset of all of our actions so that the `docs_preprocessor` can
injest it, rather than depending on the Zed crate itself to collect all
action names

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
Ben Kunkle
2025-12-18 20:59:05 -06:00
committed by GitHub
parent 435d4c5f24
commit 3f67c5220d
12 changed files with 83 additions and 74 deletions

View File

@@ -353,6 +353,9 @@ jobs:
- name: steps::download_wasi_sdk
run: ./script/download-wasi-sdk
shell: bash -euxo pipefail {0}
- name: ./script/generate-action-metadata
run: ./script/generate-action-metadata
shell: bash -euxo pipefail {0}
- name: run_tests::check_docs::install_mdbook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08
with:

1
.gitignore vendored
View File

@@ -36,6 +36,7 @@
DerivedData/
Packages
xcuserdata/
crates/docs_preprocessor/actions.json
# Don't commit any secrets to the repo.
.env

3
Cargo.lock generated
View File

@@ -5021,8 +5021,6 @@ name = "docs_preprocessor"
version = "0.1.0"
dependencies = [
"anyhow",
"command_palette",
"gpui",
"mdbook",
"regex",
"serde",
@@ -5031,7 +5029,6 @@ dependencies = [
"task",
"theme",
"util",
"zed",
"zlog",
]

View File

@@ -7,8 +7,6 @@ license = "GPL-3.0-or-later"
[dependencies]
anyhow.workspace = true
command_palette.workspace = true
gpui.workspace = true
# We are specifically pinning this version of mdbook, as later versions introduce issues with double-nested subdirectories.
# Ask @maxdeviant about this before bumping.
mdbook = "= 0.4.40"
@@ -17,7 +15,6 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
util.workspace = true
zed.workspace = true
zlog.workspace = true
task.workspace = true
theme.workspace = true

View File

@@ -22,16 +22,13 @@ static KEYMAP_WINDOWS: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap")
});
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(load_all_actions);
const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
fn main() -> Result<()> {
zlog::init();
zlog::init_output_stderr();
// 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 args = std::env::args().skip(1).collect::<Vec<_>>();
match args.get(0).map(String::as_str) {
@@ -72,8 +69,8 @@ enum PreprocessorError {
impl PreprocessorError {
fn new_for_not_found_action(action_name: String) -> Self {
for action in &*ALL_ACTIONS {
for alias in action.deprecated_aliases {
if alias == &action_name {
for alias in &action.deprecated_aliases {
if alias == action_name.as_str() {
return PreprocessorError::DeprecatedActionUsed {
used: action_name,
should_be: action.name.to_string(),
@@ -214,7 +211,7 @@ fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Prepr
chapter.content = regex
.replace_all(&chapter.content, |caps: &regex::Captures| {
let action = caps[1].trim();
if find_action_by_name(action).is_none() {
if is_missing_action(action) {
errors.insert(PreprocessorError::new_for_not_found_action(
action.to_string(),
));
@@ -244,10 +241,12 @@ fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<Preproces
.replace_all(&chapter.content, |caps: &regex::Captures| {
let name = caps[1].trim();
let Some(action) = find_action_by_name(name) else {
errors.insert(PreprocessorError::new_for_not_found_action(
name.to_string(),
));
return String::new();
if actions_available() {
errors.insert(PreprocessorError::new_for_not_found_action(
name.to_string(),
));
}
return format!("<code class=\"hljs\">{}</code>", name);
};
format!("<code class=\"hljs\">{}</code>", &action.human_name)
})
@@ -257,11 +256,19 @@ fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet<Preproces
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])
}
fn actions_available() -> bool {
!ALL_ACTIONS.is_empty()
}
fn is_missing_action(name: &str) -> bool {
actions_available() && find_action_by_name(name).is_none()
}
fn find_binding(os: &str, action: &str) -> Option<String> {
let keymap = match os {
"macos" => &KEYMAP_MACOS,
@@ -384,18 +391,13 @@ fn template_and_validate_json_snippets(book: &mut Book, errors: &mut HashSet<Pre
let keymap = settings::KeymapFile::parse(&snippet_json_fixed)
.context("Failed to parse keymap JSON")?;
for section in keymap.sections() {
for (keystrokes, action) in section.bindings() {
keystrokes
.split_whitespace()
.map(|source| gpui::Keystroke::parse(source))
.collect::<std::result::Result<Vec<_>, _>>()
.context("Failed to parse keystroke")?;
for (_keystrokes, action) in section.bindings() {
if let Some((action_name, _)) = settings::KeymapFile::parse_action(action)
.map_err(|err| anyhow::format_err!(err))
.context("Failed to parse action")?
{
anyhow::ensure!(
find_action_by_name(action_name).is_some(),
!is_missing_action(action_name),
"Action not found: {}",
action_name
);
@@ -491,27 +493,35 @@ where
});
}
#[derive(Debug, serde::Serialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ActionDef {
name: &'static str,
name: String,
human_name: String,
deprecated_aliases: &'static [&'static str],
docs: Option<&'static str>,
deprecated_aliases: Vec<String>,
#[serde(rename = "documentation")]
docs: Option<String>,
}
fn dump_all_gpui_actions() -> Vec<ActionDef> {
let mut actions = gpui::generate_list_of_all_registered_actions()
.map(|action| ActionDef {
name: action.name,
human_name: command_palette::humanize_action_name(action.name),
deprecated_aliases: action.deprecated_aliases,
docs: action.documentation,
})
.collect::<Vec<ActionDef>>();
actions.sort_by_key(|a| a.name);
actions
fn load_all_actions() -> Vec<ActionDef> {
let asset_path = concat!(env!("CARGO_MANIFEST_DIR"), "/actions.json");
match std::fs::read_to_string(asset_path) {
Ok(content) => {
let mut actions: Vec<ActionDef> =
serde_json::from_str(&content).expect("Failed to parse actions.json");
actions.sort_by(|a, b| a.name.cmp(&b.name));
actions
}
Err(err) => {
if std::env::var("CI").is_ok() {
panic!("actions.json not found at {}: {}", asset_path, err);
}
eprintln!(
"Warning: actions.json not found, action validation will be skipped: {}",
err
);
Vec::new()
}
}
}
fn handle_postprocessing() -> Result<()> {
@@ -647,7 +657,7 @@ fn generate_big_table_of_actions() -> String {
let mut output = String::new();
let mut actions_sorted = actions.iter().collect::<Vec<_>>();
actions_sorted.sort_by_key(|a| a.name);
actions_sorted.sort_by_key(|a| a.name.as_str());
// Start the definition list with custom styling for better spacing
output.push_str("<dl style=\"line-height: 1.8;\">\n");
@@ -664,7 +674,7 @@ fn generate_big_table_of_actions() -> String {
output.push_str("<dd style=\"margin-left: 2em; margin-bottom: 1em;\">\n");
// Add the description, escaping HTML if needed
if let Some(description) = action.docs {
if let Some(description) = action.docs.as_ref() {
output.push_str(
&description
.replace("&", "&amp;")
@@ -674,7 +684,7 @@ fn generate_big_table_of_actions() -> String {
output.push_str("<br>\n");
}
output.push_str("Keymap Name: <code>");
output.push_str(action.name);
output.push_str(&action.name);
output.push_str("</code><br>\n");
if !action.deprecated_aliases.is_empty() {
output.push_str("Deprecated Alias(es): ");

View File

@@ -1,12 +1,7 @@
use gpui::{Entity, OwnedMenu, OwnedMenuItem};
use gpui::{Action, Entity, OwnedMenu, OwnedMenuItem, actions};
use settings::Settings;
#[cfg(not(target_os = "macos"))]
use gpui::{Action, actions};
#[cfg(not(target_os = "macos"))]
use schemars::JsonSchema;
#[cfg(not(target_os = "macos"))]
use serde::Deserialize;
use smallvec::SmallVec;
@@ -14,18 +9,23 @@ use ui::{ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
use crate::title_bar_settings::TitleBarSettings;
#[cfg(not(target_os = "macos"))]
actions!(
app_menu,
[
/// Navigates to the menu item on the right.
/// Activates the menu on the right in the client-side application menu.
///
/// Does not apply to platform menu bars (e.g. on macOS).
ActivateMenuRight,
/// Navigates to the menu item on the left.
/// Activates the menu on the left in the client-side application menu.
///
/// Does not apply to platform menu bars (e.g. on macOS).
ActivateMenuLeft
]
);
#[cfg(not(target_os = "macos"))]
/// Opens the named menu in the client-side application menu.
///
/// Does not apply to platform menu bars (e.g. on macOS).
#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default, Action)]
#[action(namespace = app_menu)]
pub struct OpenApplicationMenu(String);

View File

@@ -15,10 +15,6 @@ tracy = ["ztracing/tracy"]
[[bin]]
name = "zed"
path = "src/zed-main.rs"
[lib]
name = "zed"
path = "src/main.rs"
[dependencies]

View File

@@ -1,3 +1,6 @@
// Disable command line from opening on release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod reliability;
mod zed;
@@ -163,9 +166,9 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
.detach();
}
}
pub static STARTUP_TIME: OnceLock<Instant> = OnceLock::new();
static STARTUP_TIME: OnceLock<Instant> = OnceLock::new();
pub fn main() {
fn main() {
STARTUP_TIME.get_or_init(|| Instant::now());
#[cfg(unix)]
@@ -1301,7 +1304,7 @@ fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
})
}
pub fn stdout_is_a_pty() -> bool {
fn stdout_is_a_pty() -> bool {
std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
}
@@ -1547,14 +1550,14 @@ fn dump_all_gpui_actions() {
struct ActionDef {
name: &'static str,
human_name: String,
aliases: &'static [&'static str],
deprecated_aliases: &'static [&'static str],
documentation: Option<&'static str>,
}
let mut actions = gpui::generate_list_of_all_registered_actions()
.map(|action| ActionDef {
name: action.name,
human_name: command_palette::humanize_action_name(action.name),
aliases: action.deprecated_aliases,
deprecated_aliases: action.deprecated_aliases,
documentation: action.documentation,
})
.collect::<Vec<ActionDef>>();

View File

@@ -1,8 +0,0 @@
// Disable command line from opening on release mode
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
pub fn main() {
// separated out so that the file containing the main function can be imported by other crates,
// while having all gpui resources that are registered in main (primarily actions) initialized
zed::main();
}

View File

@@ -4780,7 +4780,6 @@ mod tests {
"activity_indicator",
"agent",
"agents",
#[cfg(not(target_os = "macos"))]
"app_menu",
"assistant",
"assistant2",

10
script/generate-action-metadata Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
echo "Generating action metadata..."
cargo run -p zed -- --dump-all-actions > crates/docs_preprocessor/actions.json
echo "Generated crates/docs_preprocessor/actions.json with $(grep -c '"name":' crates/docs_preprocessor/actions.json) actions"

View File

@@ -448,6 +448,7 @@ fn check_docs() -> NamedJob {
lychee_link_check("./docs/src/**/*"), // check markdown links
)
.map(steps::install_linux_dependencies)
.add_step(steps::script("./script/generate-action-metadata"))
.add_step(install_mdbook())
.add_step(build_docs())
.add_step(