diff --git a/.github/actions/build_docs/action.yml b/.github/actions/build_docs/action.yml new file mode 100644 index 0000000000..27f0f37d4f --- /dev/null +++ b/.github/actions/build_docs/action.yml @@ -0,0 +1,26 @@ +name: "Build docs" +description: "Build the docs" + +runs: + using: "composite" + steps: + - name: Setup mdBook + uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2 + with: + mdbook-version: "0.4.37" + + - name: Cache dependencies + uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + cache-provider: "buildjet" + + - name: Install Linux dependencies + shell: bash -euxo pipefail {0} + run: ./script/linux + + - name: Build book + shell: bash -euxo pipefail {0} + run: | + mkdir -p target/deploy + mdbook build ./docs --dest-dir=../target/deploy/docs/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9414d2ea..c154505811 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,6 +191,27 @@ jobs: with: config: ./typos.toml + check_docs: + timeout-minutes: 60 + name: Check docs + needs: [job_spec] + if: github.repository_owner == 'zed-industries' + runs-on: + - buildjet-8vcpu-ubuntu-2204 + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + clean: false + + - name: Configure CI + run: | + mkdir -p ./../.cargo + cp ./.cargo/ci-config.toml ./../.cargo/config.toml + + - name: Build docs + uses: ./.github/actions/build_docs + macos_tests: timeout-minutes: 60 name: (macOS) Run Clippy and tests diff --git a/.github/workflows/deploy_cloudflare.yml b/.github/workflows/deploy_cloudflare.yml index 9222228d78..fe443d493e 100644 --- a/.github/workflows/deploy_cloudflare.yml +++ b/.github/workflows/deploy_cloudflare.yml @@ -9,7 +9,7 @@ jobs: deploy-docs: name: Deploy Docs if: github.repository_owner == 'zed-industries' - runs-on: ubuntu-latest + runs-on: buildjet-16vcpu-ubuntu-2204 steps: - name: Checkout repo @@ -17,24 +17,11 @@ jobs: with: clean: false - - name: Setup mdBook - uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2 - with: - mdbook-version: "0.4.37" - - name: Set up default .cargo/config.toml run: cp ./.cargo/collab-config.toml ./.cargo/config.toml - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install libxkbcommon-dev libxkbcommon-x11-dev - - - name: Build book - run: | - set -euo pipefail - mkdir -p target/deploy - mdbook build ./docs --dest-dir=../target/deploy/docs/ + - name: Build docs + uses: ./.github/actions/build_docs - name: Deploy Docs uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3 diff --git a/Cargo.lock b/Cargo.lock index 288bc81a57..07f629a653 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4543,6 +4543,8 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "command_palette", + "gpui", "mdbook", "regex", "serde", @@ -4550,6 +4552,7 @@ dependencies = [ "settings", "util", "workspace-hack", + "zed", ] [[package]] diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index f7dd30012b..1d0972c92f 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -120,7 +120,7 @@ "ctrl-'": "editor::ToggleSelectedDiffHunks", "ctrl-\"": "editor::ExpandAllDiffHunks", "ctrl-i": "editor::ShowSignatureHelp", - "alt-g b": "editor::ToggleGitBlame", + "alt-g b": "git::Blame", "menu": "editor::OpenContextMenu", "shift-f10": "editor::OpenContextMenu", "ctrl-shift-e": "editor::ToggleEditPrediction", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 8e3e895d11..833547ea6b 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -138,7 +138,7 @@ "cmd-;": "editor::ToggleLineNumbers", "cmd-'": "editor::ToggleSelectedDiffHunks", "cmd-\"": "editor::ExpandAllDiffHunks", - "cmd-alt-g b": "editor::ToggleGitBlame", + "cmd-alt-g b": "git::Blame", "cmd-i": "editor::ShowSignatureHelp", "f9": "editor::ToggleBreakpoint", "shift-f9": "editor::EditLogBreakpoint", diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 9c88af9d16..bafe611791 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -448,7 +448,7 @@ impl PickerDelegate for CommandPaletteDelegate { } } -fn humanize_action_name(name: &str) -> String { +pub fn humanize_action_name(name: &str) -> String { let capacity = name.len() + name.chars().filter(|c| c.is_uppercase()).count(); let mut result = String::with_capacity(capacity); for char in name.chars() { diff --git a/crates/docs_preprocessor/Cargo.toml b/crates/docs_preprocessor/Cargo.toml index a77965ce1d..a0df669abe 100644 --- a/crates/docs_preprocessor/Cargo.toml +++ b/crates/docs_preprocessor/Cargo.toml @@ -15,6 +15,9 @@ settings.workspace = true regex.workspace = true util.workspace = true workspace-hack.workspace = true +zed.workspace = true +gpui.workspace = true +command_palette.workspace = true [lints] workspace = true diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index a6962e9bb0..c76ffd52a5 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -5,6 +5,7 @@ use mdbook::book::{Book, Chapter}; use mdbook::preprocess::CmdPreprocessor; use regex::Regex; use settings::KeymapFile; +use std::collections::HashSet; use std::io::{self, Read}; use std::process; use std::sync::LazyLock; @@ -17,6 +18,8 @@ static KEYMAP_LINUX: LazyLock = LazyLock::new(|| { load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap") }); +static ALL_ACTIONS: LazyLock> = LazyLock::new(dump_all_gpui_actions); + pub fn make_app() -> Command { Command::new("zed-docs-preprocessor") .about("Preprocesses Zed Docs content to provide rich action & keybinding support and more") @@ -29,6 +32,9 @@ 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); @@ -39,6 +45,43 @@ fn main() -> Result<()> { Ok(()) } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum Error { + ActionNotFound { action_name: String }, + DeprecatedActionUsed { used: String, should_be: String }, +} + +impl Error { + 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 { + return Error::DeprecatedActionUsed { + used: action_name.clone(), + should_be: action.name.to_string(), + }; + } + } + } + Error::ActionNotFound { + action_name: action_name.to_string(), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ActionNotFound { action_name } => write!(f, "Action not found: {}", action_name), + Error::DeprecatedActionUsed { used, should_be } => write!( + f, + "Deprecated action used: {} should be {}", + used, should_be + ), + } + } +} + fn handle_preprocessing() -> Result<()> { let mut stdin = io::stdin(); let mut input = String::new(); @@ -46,8 +89,19 @@ fn handle_preprocessing() -> Result<()> { let (_ctx, mut book) = CmdPreprocessor::parse_input(input.as_bytes())?; - template_keybinding(&mut book); - template_action(&mut book); + let mut errors = HashSet::::new(); + + template_and_validate_keybindings(&mut book, &mut errors); + template_and_validate_actions(&mut book, &mut errors); + + if !errors.is_empty() { + const ANSI_RED: &'static str = "\x1b[31m"; + const ANSI_RESET: &'static str = "\x1b[0m"; + for error in &errors { + eprintln!("{ANSI_RED}ERROR{ANSI_RESET}: {}", error); + } + return Err(anyhow::anyhow!("Found {} errors in docs", errors.len())); + } serde_json::to_writer(io::stdout(), &book)?; @@ -66,13 +120,17 @@ fn handle_supports(sub_args: &ArgMatches) -> ! { } } -fn template_keybinding(book: &mut Book) { +fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet) { let regex = Regex::new(r"\{#kb (.*?)\}").unwrap(); for_each_chapter_mut(book, |chapter| { chapter.content = regex .replace_all(&chapter.content, |caps: ®ex::Captures| { let action = caps[1].trim(); + if find_action_by_name(action).is_none() { + errors.insert(Error::new_for_not_found_action(action.to_string())); + return String::new(); + } let macos_binding = find_binding("macos", action).unwrap_or_default(); let linux_binding = find_binding("linux", action).unwrap_or_default(); @@ -86,35 +144,30 @@ fn template_keybinding(book: &mut Book) { }); } -fn template_action(book: &mut Book) { +fn template_and_validate_actions(book: &mut Book, errors: &mut HashSet) { let regex = Regex::new(r"\{#action (.*?)\}").unwrap(); for_each_chapter_mut(book, |chapter| { chapter.content = regex .replace_all(&chapter.content, |caps: ®ex::Captures| { let name = caps[1].trim(); - - let formatted_name = name - .chars() - .enumerate() - .map(|(i, c)| { - if i > 0 && c.is_uppercase() { - format!(" {}", c.to_lowercase()) - } else { - c.to_string() - } - }) - .collect::() - .trim() - .to_string() - .replace("::", ":"); - - format!("{}", formatted_name) + let Some(action) = find_action_by_name(name) else { + errors.insert(Error::new_for_not_found_action(name.to_string())); + return String::new(); + }; + format!("{}", &action.human_name) }) .into_owned() }); } +fn find_action_by_name(name: &str) -> Option<&ActionDef> { + ALL_ACTIONS + .binary_search_by(|action| action.name.cmp(name)) + .ok() + .map(|index| &ALL_ACTIONS[index]) +} + fn find_binding(os: &str, action: &str) -> Option { let keymap = match os { "macos" => &KEYMAP_MACOS, @@ -180,3 +233,25 @@ where func(chapter); }); } + +#[derive(Debug, serde::Serialize)] +struct ActionDef { + name: &'static str, + human_name: String, + deprecated_aliases: &'static [&'static str], +} + +fn dump_all_gpui_actions() -> Vec { + 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::>(); + + actions.sort_by_key(|a| a.name); + + return actions; +} diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index d7b97ce91d..db617758b3 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -288,6 +288,18 @@ impl ActionRegistry { } } +/// Generate a list of all the registered actions. +/// Useful for transforming the list of available actions into a +/// format suited for static analysis such as in validating keymaps, or +/// generating documentation. +pub fn generate_list_of_all_registered_actions() -> Vec { + let mut actions = Vec::new(); + for builder in inventory::iter:: { + actions.push(builder.0()); + } + actions +} + /// Defines and registers unit structs that can be used as actions. /// /// To use more complex data types as actions, use `impl_actions!` @@ -333,7 +345,6 @@ macro_rules! action_as { ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, )] pub struct $name; - gpui::__impl_action!( $namespace, $name, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 9c7a1c554a..c40ea4cb98 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -12,6 +12,10 @@ workspace = true [[bin]] name = "zed" +path = "src/zed-main.rs" + +[lib] +name = "zed" path = "src/main.rs" [dependencies] diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 04bd9b7140..532963fadf 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -163,7 +163,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) { } } -fn main() { +pub fn main() { #[cfg(unix)] { let is_root = nix::unistd::geteuid().is_root(); @@ -199,6 +199,11 @@ Error: Running Zed as root or via sudo is unsupported. return; } + if args.dump_all_actions { + dump_all_gpui_actions(); + return; + } + // Set custom data directory. if let Some(dir) = &args.user_data_dir { paths::set_custom_data_dir(dir); @@ -213,9 +218,6 @@ Error: Running Zed as root or via sudo is unsupported. } } - menu::init(); - zed_actions::init(); - let file_errors = init_paths(); if !file_errors.is_empty() { files_not_created_on_launch(file_errors); @@ -356,6 +358,9 @@ Error: Running Zed as root or via sudo is unsupported. }); app.run(move |cx| { + menu::init(); + zed_actions::init(); + release_channel::init(app_version, cx); gpui_tokio::init(cx); if let Some(app_commit_sha) = app_commit_sha { @@ -1018,7 +1023,7 @@ fn init_paths() -> HashMap> { }) } -fn stdout_is_a_pty() -> bool { +pub fn stdout_is_a_pty() -> bool { std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal() } @@ -1055,7 +1060,7 @@ struct Args { #[arg(long, hide = true)] askpass: Option, - /// Run zed in the foreground, only used on Windows, to match the behavior of the behavior on macOS. + /// Run zed in the foreground, only used on Windows, to match the behavior on macOS. #[arg(long)] #[cfg(target_os = "windows")] #[arg(hide = true)] @@ -1066,6 +1071,9 @@ struct Args { #[cfg(target_os = "windows")] #[arg(hide = true)] dock_action: Option, + + #[arg(long, hide = true)] + dump_all_actions: bool, } #[derive(Clone, Debug)] @@ -1278,3 +1286,28 @@ fn watch_languages(fs: Arc, languages: Arc, cx: &m #[cfg(not(debug_assertions))] fn watch_languages(_fs: Arc, _languages: Arc, _cx: &mut App) {} + +fn dump_all_gpui_actions() { + #[derive(Debug, serde::Serialize)] + struct ActionDef { + name: &'static str, + human_name: String, + aliases: &'static [&'static str], + } + 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), + aliases: action.aliases, + }) + .collect::>(); + + actions.sort_by_key(|a| a.name); + + io::Write::write( + &mut std::io::stdout(), + serde_json::to_string_pretty(&actions).unwrap().as_bytes(), + ) + .unwrap(); +} diff --git a/crates/zed/src/zed-main.rs b/crates/zed/src/zed-main.rs new file mode 100644 index 0000000000..051d02802e --- /dev/null +++ b/crates/zed/src/zed-main.rs @@ -0,0 +1,5 @@ +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(); +} diff --git a/docs/src/git.md b/docs/src/git.md index a7dcfbefe2..69d87ddf66 100644 --- a/docs/src/git.md +++ b/docs/src/git.md @@ -120,7 +120,7 @@ or by simply right clicking and selecting `Copy Permalink` with line(s) selected | {#action git::Branch} | {#kb git::Branch} | | {#action git::Switch} | {#kb git::Switch} | | {#action git::CheckoutBranch} | {#kb git::CheckoutBranch} | -| {#action editor::ToggleGitBlame} | {#kb editor::ToggleGitBlame} | +| {#action git::Blame} | {#kb git::Blame} | | {#action editor::ToggleGitBlameInline} | {#kb editor::ToggleGitBlameInline} | > Not all actions have default keybindings, but can be bound by [customizing your keymap](./key-bindings.md#user-keymaps).