Detailed changes
@@ -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/
@@ -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
@@ -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
@@ -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]]
@@ -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",
@@ -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",
@@ -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() {
@@ -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
@@ -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<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);
+
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::<Error>::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<Error>) {
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<Error>) {
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::<String>()
- .trim()
- .to_string()
- .replace("::", ":");
-
- format!("<code class=\"hljs\">{}</code>", 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!("<code class=\"hljs\">{}</code>", &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<String> {
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<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>>();
+
+ actions.sort_by_key(|a| a.name);
+
+ return actions;
+}
@@ -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<MacroActionData> {
+ let mut actions = Vec::new();
+ for builder in inventory::iter::<MacroActionBuilder> {
+ 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,
@@ -12,6 +12,10 @@ workspace = true
[[bin]]
name = "zed"
+path = "src/zed-main.rs"
+
+[lib]
+name = "zed"
path = "src/main.rs"
[dependencies]
@@ -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<io::ErrorKind, Vec<&'static Path>> {
})
}
-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<String>,
- /// 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<usize>,
+
+ #[arg(long, hide = true)]
+ dump_all_actions: bool,
}
#[derive(Clone, Debug)]
@@ -1278,3 +1286,28 @@ fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &m
#[cfg(not(debug_assertions))]
fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _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::<Vec<ActionDef>>();
+
+ actions.sort_by_key(|a| a.name);
+
+ io::Write::write(
+ &mut std::io::stdout(),
+ serde_json::to_string_pretty(&actions).unwrap().as_bytes(),
+ )
+ .unwrap();
+}
@@ -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();
+}
@@ -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).