my friend claude is great at ~~stealing~~ taking inspiration from hidden

cameron created

gems

Change summary

crates/editor/src/editor.rs                                       | 104 +
crates/file_finder/src/file_finder.rs                             |  17 
crates/language_selector/src/language_selector.rs                 |  17 
crates/outline/src/outline.rs                                     |  17 
crates/project_panel/src/project_panel.rs                         |  16 
crates/search/src/search.rs                                       |  31 
crates/settings_profile_selector/src/settings_profile_selector.rs |  17 
crates/tasks_ui/src/tasks_ui.rs                                   |  31 
crates/terminal_view/src/terminal_view.rs                         |  17 
crates/vim/src/vim.rs                                             |  12 
crates/workspace/src/workspace.rs                                 |  76 
11 files changed, 346 insertions(+), 9 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -332,6 +332,46 @@ pub enum HideMouseCursorOrigin {
     MovementAction,
 }
 
+const CURSOR_FOR_EVERY_LINE_TIP_MESSAGE: &str = "\
+Need a cursor on every line to manipulate text in bulk? \
+Select a block of text, then use `editor: split selection into lines` \
+to place a cursor at the end of each line. \
+Now you can edit all lines simultaneously with multicursors.";
+
+const COPY_AND_TRIM_TIP_MESSAGE: &str = "\
+When copying indented code, extra leading whitespace can be annoying. \
+Use `editor: copy and trim` instead of a regular copy \u{2014} \
+it strips the common leading indentation from the selected lines, \
+giving you a cleaner paste.";
+
+const DIFF_CLIPBOARD_TIP_MESSAGE: &str = "\
+Found two similar code blocks and need to spot the differences? \
+Copy one block to your clipboard, select the other, then run \
+`editor: diff clipboard with selection` from the command palette. \
+The diff view shows exactly what varies between them.";
+
+const SELECTIONS_IN_MULTIBUFFER_TIP_MESSAGE: &str = "\
+When using multicursors across a large file, reviewing scattered \
+selections can be tedious. Run `editor: open selections in multibuffer` \
+to consolidate all your selections into a compact view where \
+they're just a few lines apart.";
+
+const QUICK_NEW_LINES_TIP_MESSAGE: &str = "\
+No need to move your cursor to the end of a line before pressing Enter. \
+Use `editor: newline below` to start a new line below the current one, \
+or `editor: newline above` to insert one above \u{2014} \
+your cursor jumps right to the new line.";
+
+const RUN_NEAREST_TEST_TIP_MESSAGE: &str = "\
+Iterating on a test? Use `editor: spawn nearest task` to run the test \
+closest to your cursor. It's the most convenient keyboard-driven way \
+to repeatedly run a single test without navigating to the terminal.";
+
+const SELECT_ALL_MATCHES_TIP_MESSAGE: &str = "\
+Place your cursor on a word and use `editor: select all matches` \
+to instantly create selections on every occurrence of that word \
+in the buffer. Then edit them all at once with multicursors.";
+
 pub fn init(cx: &mut App) {
     cx.set_global(GlobalBlameRenderer(Arc::new(())));
     cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
@@ -387,6 +427,70 @@ pub fn init(cx: &mut App) {
         )) as Arc<dyn ErasedEditor>
     });
     _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
+
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "A Cursor for Every Line".into(),
+            message: CURSOR_FOR_EVERY_LINE_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::CursorIBeam),
+            mentioned_actions: vec![Box::new(SplitSelectionIntoLines::default())],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Copy and Trim".into(),
+            message: COPY_AND_TRIM_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Copy),
+            mentioned_actions: vec![Box::new(CopyAndTrim)],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Diff Clipboard With Selection".into(),
+            message: DIFF_CLIPBOARD_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Diff),
+            mentioned_actions: vec![Box::new(DiffClipboardWithSelection)],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Gather Your Selections in a Multibuffer".into(),
+            message: SELECTIONS_IN_MULTIBUFFER_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::SelectAll),
+            mentioned_actions: vec![Box::new(OpenSelectionsInMultibuffer)],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Quick New Lines Above and Below".into(),
+            message: QUICK_NEW_LINES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Return),
+            mentioned_actions: vec![Box::new(NewlineBelow), Box::new(NewlineAbove)],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Run the Nearest Test".into(),
+            message: RUN_NEAREST_TEST_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::PlayFilled),
+            mentioned_actions: vec![Box::new(SpawnNearestTask::default())],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Select All Matches".into(),
+            message: SELECT_ALL_MATCHES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::SelectAll),
+            mentioned_actions: vec![Box::new(SelectAllMatches)],
+        },
+        cx,
+    );
 }
 
 pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {

crates/file_finder/src/file_finder.rs 🔗

@@ -88,7 +88,24 @@ pub struct FileFinder {
     init_modifiers: Option<Modifiers>,
 }
 
+const TELESCOPE_TIP_MESSAGE: &str = "\
+Prefer a fuzzy file finder like Vim's Telescope? Install `television` on \
+your system, then create a custom task in `tasks.json` with \
+`\"command\": \"zed \\\"$(tv files)\\\"\"` and `\"hide\": \"always\"`. Bind it to \
+`cmd-p` / `ctrl-p` using `task::Spawn` for a Telescope-like experience \
+right in Zed.";
+
 pub fn init(cx: &mut App) {
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Emulate Vim's Telescope".into(),
+            message: TELESCOPE_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::MagnifyingGlass),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+
     cx.observe_new(FileFinder::register).detach();
     cx.observe_new(OpenPathPrompt::register).detach();
     cx.observe_new(OpenPathPrompt::register_new_path).detach();

crates/language_selector/src/language_selector.rs 🔗

@@ -26,7 +26,24 @@ actions!(
     ]
 );
 
+const FILE_TYPES_TIP_MESSAGE: &str = "\
+Zed doesn't recognize your file extension? Use the `file_types` setting to \
+map it permanently. For example, `\"file_types\": { \"JavaScript\": \
+[\"*.jsm\"] }` makes Zed treat `.jsm` files as JavaScript — with full \
+syntax highlighting and language server support. Check the extensions store \
+first, though, in case direct support already exists.";
+
 pub fn init(cx: &mut App) {
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Map Unknown File Types to Known Languages".into(),
+            message: FILE_TYPES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::FileCode),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+
     cx.observe_new(LanguageSelector::register).detach();
 }
 

crates/outline/src/outline.rs 🔗

@@ -22,6 +22,13 @@ use ui::{ListItem, ListItemSpacing, prelude::*};
 use util::ResultExt;
 use workspace::{DismissDecision, ModalView};
 
+const OUTLINE_TIP_MESSAGE: &str = "\
+The outline modal (`outline: toggle`) filters symbols by name. But if you include \
+a space in your query, it also matches contextual keywords like visibility modifiers \
+and declaration types. Try queries like `pub fn` to find all public functions, \
+`trait ` (with a trailing space) for trait definitions, or `impl Foo for` to find \
+trait implementations.";
+
 pub fn init(cx: &mut App) {
     cx.observe_new(OutlineView::register).detach();
     zed_actions::outline::TOGGLE_OUTLINE
@@ -33,6 +40,16 @@ pub fn init(cx: &mut App) {
             toggle(editor, &Default::default(), window, cx);
         })
         .ok();
+
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Filter by Context in the Outline".into(),
+            message: OUTLINE_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::ListTree),
+            mentioned_actions: vec![Box::new(zed_actions::outline::ToggleOutline)],
+        },
+        cx,
+    );
 }
 
 pub fn toggle(

crates/project_panel/src/project_panel.rs 🔗

@@ -443,7 +443,23 @@ impl FoldedAncestors {
     }
 }
 
+const COMPARE_MARKED_FILES_TIP_MESSAGE: &str = "\
+Need to compare two files? In the project panel, select multiple files \
+(hold `cmd`/`ctrl` while clicking), then right-click and choose \
+\"Compare Marked Files\" to open a diff view of the two most recently \
+selected files.";
+
 pub fn init(cx: &mut App) {
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Compare Marked Files".into(),
+            message: COMPARE_MARKED_FILES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Diff),
+            mentioned_actions: vec![Box::new(CompareMarkedFiles)],
+        },
+        cx,
+    );
+
     cx.observe_new(|workspace: &mut Workspace, _, _| {
         workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
             workspace.toggle_panel_focus::<ProjectPanel>(window, cx);

crates/search/src/search.rs 🔗

@@ -20,10 +20,41 @@ pub mod project_search;
 pub(crate) mod search_bar;
 pub mod search_status_button;
 
+const SELECT_ALL_REGEX_MATCHES_TIP_MESSAGE: &str = "\
+Need to edit every occurrence of a complex pattern? Open buffer search, enable the regex \
+filter, type your pattern, then press `alt-enter` (or click the 'Select All Matches' button). \
+Every match becomes a selection you can edit simultaneously \u{2014} far more powerful than simple \
+find-and-replace.";
+
+const MULTIPLE_PROJECT_SEARCH_TABS_TIP_MESSAGE: &str = "\
+By default, project search reuses a single tab. Want to keep multiple searches open? In an \
+existing search tab, type your new query and press `cmd-enter` (macOS) / `ctrl-enter` \
+(Windows/Linux) to open results in a new tab. You can also rebind `cmd-shift-f` to \
+`workspace::NewSearch` for fresh tabs every time.";
+
 pub fn init(cx: &mut App) {
     menu::init();
     buffer_search::init(cx);
     project_search::init(cx);
+
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Select All Regex Matches".into(),
+            message: SELECT_ALL_REGEX_MATCHES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Regex),
+            mentioned_actions: vec![Box::new(SelectAllMatches)],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Multiple Project Search Tabs".into(),
+            message: MULTIPLE_PROJECT_SEARCH_TABS_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::MagnifyingGlass),
+            mentioned_actions: vec![Box::new(workspace::NewSearch)],
+        },
+        cx,
+    );
 }
 
 actions!(

crates/settings_profile_selector/src/settings_profile_selector.rs 🔗

@@ -7,7 +7,24 @@ use settings::{ActiveSettingsProfileName, SettingsStore};
 use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
 use workspace::{ModalView, Workspace};
 
+const SETTINGS_PROFILES_TIP_MESSAGE: &str = "\
+Define settings profiles in your `settings.json` to switch configurations \
+on the fly. Create named profiles under `\"profiles\"` with any settings \
+you want \u{2014} font sizes, themes, panel layouts \u{2014} then toggle between them \
+with `settings profile selector: toggle`. Great for switching between \
+coding, presenting, and writing modes.";
+
 pub fn init(cx: &mut App) {
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Quick-Swap Settings with Profiles".into(),
+            message: SETTINGS_PROFILES_TIP_MESSAGE.into(),
+            icon: Some(IconName::Settings),
+            mentioned_actions: vec![Box::new(zed_actions::settings_profile_selector::Toggle)],
+        },
+        cx,
+    );
+
     cx.on_action(|_: &zed_actions::settings_profile_selector::Toggle, cx| {
         workspace::with_active_or_new_workspace(cx, |workspace, window, cx| {
             toggle_settings_profile_selector(workspace, window, cx);

crates/tasks_ui/src/tasks_ui.rs 🔗

@@ -9,6 +9,18 @@ use workspace::Workspace;
 
 mod modal;
 
+const CUSTOMIZE_TASKS_TIP_MESSAGE: &str = "\
+Need to tweak a task before running it? In the task modal (`task: spawn`), press \
+`tab` to expand any task into its full command. Edit it however you like \u{2014} for example, \
+add `RUST_BACKTRACE=1` \u{2014} then press `alt-enter` to run it as a oneshot task. The \
+modified task is saved to your history for easy reuse.";
+
+const LAZYGIT_TASK_TIP_MESSAGE: &str = "\
+Want a full-featured Git TUI inside Zed? Add a Lazygit task to your `tasks.json` \
+with `\"command\": \"lazygit\"`, `\"reveal_target\": \"center\"`, and \
+`\"use_new_terminal\": true`. Then bind it to a keybinding like `cmd-shift-g` using \
+`task::Spawn` with `{ \"task_name\": \"LazyGit\" }` for instant access.";
+
 pub use modal::{Rerun, ShowAttachModal, Spawn, TaskOverrides, TasksModal};
 
 pub fn init(cx: &mut App) {
@@ -93,6 +105,25 @@ pub fn init(cx: &mut App) {
         },
     )
     .detach();
+
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Customize Tasks on the Fly".into(),
+            message: CUSTOMIZE_TASKS_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Terminal),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Lazygit as a Task".into(),
+            message: LAZYGIT_TASK_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Terminal),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
 }
 
 fn spawn_task_or_modal(

crates/terminal_view/src/terminal_view.rs 🔗

@@ -97,10 +97,27 @@ actions!(
 #[action(namespace = terminal)]
 pub struct RenameTerminal;
 
+const TMUX_SESSION_TIP_MESSAGE: &str = "\
+If you use tmux, configure Zed's terminal to automatically attach to a \
+project-specific session. Set `\"terminal\": { \"shell\": { \
+\"with_arguments\": { \"program\": \"/bin/zsh\", \"args\": [\"-c\", \
+\"tmux new-session -A -s \\\"$(basename \\\"$PWD\\\")\\\"\"] } } }` in \
+your settings. Each project gets its own persistent tmux session.";
+
 pub fn init(cx: &mut App) {
     assistant_slash_command::init(cx);
     terminal_panel::init(cx);
 
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Project-Specific Tmux Sessions".into(),
+            message: TMUX_SESSION_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Terminal),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+
     register_serializable_item::<TerminalView>(cx);
 
     cx.observe_new(|workspace: &mut Workspace, _window, _cx| {

crates/vim/src/vim.rs 🔗

@@ -275,10 +275,22 @@ actions!(
     ]
 );
 
+const SEND_KEYSTROKES_TIP_MESSAGE: &str = "In Vim mode, you can map keybindings to replay sequences of keystrokes — just like Vim macros but permanent. Use `workspace::SendKeystrokes` in your keymap. For example, in visual mode, `\"\\d\": [\"workspace::SendKeystrokes\", \"\\\" a s dbg!( ctrl-r a ) escape\"]` wraps a selection with `dbg!()` in Rust.";
+
 /// Initializes the `vim` crate.
 pub fn init(cx: &mut App) {
     VimGlobals::register(cx);
 
+    workspace::welcome::register_tip(
+        workspace::welcome::Tip {
+            title: "Custom Vim Macros with SendKeystrokes".into(),
+            message: SEND_KEYSTROKES_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Keyboard),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+
     cx.observe_new(Vim::register).detach();
 
     cx.observe_new(|workspace: &mut Workspace, _, _| {

crates/workspace/src/workspace.rs 🔗

@@ -717,14 +717,36 @@ pub fn prompt_for_open_path_and_open(
     .detach();
 }
 
-const MESSAGE: &str = r#"
-command palette is good. use it
-
-hey look markdown:
-```json
-{"fun": "times"}
-```
-"#;
+const COMMAND_PALETTE_TIP_MESSAGE: &str = "\
+The command palette is your gateway to every action in Zed. \
+Open it to quickly find and run any command, toggle settings, \
+or navigate your project \u{2014} all without leaving the keyboard.";
+
+const PROSE_WRITING_TIP_MESSAGE: &str = "\
+Want a distraction-free writing environment? \
+Use `workspace: close all docks` to hide panels, \
+`workspace: close inactive tabs and panes` to clean up, \
+then `workspace: toggle centered layout` to center your content. \
+Chain all three with `action::Sequence` in your keymap for a one-key zen mode.";
+
+const SEQUENCE_ACTIONS_TIP_MESSAGE: &str = "\
+Want one keybinding to do multiple things? \
+Use `action::Sequence` in your keymap to chain actions together. \
+For example, you could close all docks, clean up tabs, \
+and toggle centered layout \u{2014} all with a single keystroke.";
+
+const AUTOSAVE_TIP_MESSAGE: &str = "\
+Tired of forgetting to save before running tests? \
+Add `\"autosave\": \"on_focus_change\"` to your settings, \
+and Zed will automatically save your files whenever you switch \
+to a terminal or another application.";
+
+const OPEN_IN_NEW_PANE_TIP_MESSAGE: &str = "\
+When opening a file from the project panel or file finder, \
+hold `cmd` (macOS) or `ctrl` (Linux/Windows) to open it in a new pane \
+to the right instead of replacing your current tab. \
+When jumping to a definition, hold `alt` in addition to `cmd`/`ctrl` \
+for the same effect.";
 
 pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     component::init();
@@ -735,12 +757,48 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     welcome::register_tip(
         welcome::Tip {
             title: "Master the Command Palette".into(),
-            message: MESSAGE.into(),
+            message: COMMAND_PALETTE_TIP_MESSAGE.into(),
             icon: Some(ui::IconName::Sparkle),
             mentioned_actions: vec![Box::new(zed_actions::command_palette::Toggle)],
         },
         cx,
     );
+    welcome::register_tip(
+        welcome::Tip {
+            title: "Set Up a Prose-Writing Focus".into(),
+            message: PROSE_WRITING_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::CursorIBeam),
+            mentioned_actions: vec![Box::new(ToggleCenteredLayout)],
+        },
+        cx,
+    );
+    welcome::register_tip(
+        welcome::Tip {
+            title: "Sequence Multiple Actions".into(),
+            message: SEQUENCE_ACTIONS_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::PlayFilled),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
+    welcome::register_tip(
+        welcome::Tip {
+            title: "Auto-Save on Focus Change".into(),
+            message: AUTOSAVE_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Settings),
+            mentioned_actions: vec![Box::new(zed_actions::OpenSettings)],
+        },
+        cx,
+    );
+    welcome::register_tip(
+        welcome::Tip {
+            title: "Open Files in a New Pane".into(),
+            message: OPEN_IN_NEW_PANE_TIP_MESSAGE.into(),
+            icon: Some(ui::IconName::Split),
+            mentioned_actions: vec![],
+        },
+        cx,
+    );
 
     cx.on_action(|_: &CloseWindow, cx| Workspace::close_global(cx))
         .on_action(|_: &Reload, cx| reload(cx))