diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb15cd2c2d..6b62ed3e1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,6 @@ jobs: MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} - ZED_AMPLITUDE_API_KEY: ${{ secrets.ZED_AMPLITUDE_API_KEY }} ZED_MIXPANEL_TOKEN: ${{ secrets.ZED_MIXPANEL_TOKEN }} steps: - name: Install Rust diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 17cb8864e1..65866baf7f 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -8,6 +8,7 @@ jobs: steps: - name: Discord Webhook Action uses: tsickert/discord-webhook@v5.3.0 + if: ${{ ! github.event.release.prerelease }} with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} content: | @@ -20,7 +21,7 @@ jobs: ${{ github.event.release.body }} ``` - amplitude_release: + mixpanel_release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -29,5 +30,10 @@ jobs: python-version: "3.10.5" architecture: "x64" cache: "pip" - - run: pip install -r script/amplitude_release/requirements.txt - - run: python script/amplitude_release/main.py ${{ github.event.release.tag_name }} ${{ secrets.ZED_AMPLITUDE_API_KEY }} ${{ secrets.ZED_AMPLITUDE_SECRET_KEY }} + - run: pip install -r script/mixpanel_release/requirements.txt + - run: > + python script/mixpanel_release/main.py + ${{ github.event.release.tag_name }} + ${{ secrets.MIXPANEL_PROJECT_ID }} + ${{ secrets.MIXPANEL_SERVICE_ACCOUNT_USERNAME }} + ${{ secrets.MIXPANEL_SERVICE_ACCOUNT_SECRET }} diff --git a/Cargo.lock b/Cargo.lock index c1453e0e10..c1a176ca3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4839,8 +4839,7 @@ dependencies = [ [[package]] name = "rusqlite_migration" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda44233be97aea786691f9f6f7ef230bcf905061f4012e90f4f39e6dcf31163" +source = "git+https://github.com/cljoly/rusqlite_migration?rev=c433555d7c1b41b103426e35756eb3144d0ebbc6#c433555d7c1b41b103426e35756eb3144d0ebbc6" dependencies = [ "log", "rusqlite", @@ -7629,7 +7628,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.62.0" +version = "0.62.8" dependencies = [ "activity_indicator", "anyhow", diff --git a/assets/icons/arrow_down_12.svg b/assets/icons/arrow_down_12.svg index 5d9a8ee5e3..dfad5d4876 100644 --- a/assets/icons/arrow_down_12.svg +++ b/assets/icons/arrow_down_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_down_16.svg b/assets/icons/arrow_down_16.svg index 65114ebd60..ec757a8ab4 100644 --- a/assets/icons/arrow_down_16.svg +++ b/assets/icons/arrow_down_16.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/arrow_down_8.svg b/assets/icons/arrow_down_8.svg index 53d3c2944e..f70f3920a3 100644 --- a/assets/icons/arrow_down_8.svg +++ b/assets/icons/arrow_down_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_left_12.svg b/assets/icons/arrow_left_12.svg index fc7cabc9c6..aaccf25eaf 100644 --- a/assets/icons/arrow_left_12.svg +++ b/assets/icons/arrow_left_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_left_16.svg b/assets/icons/arrow_left_16.svg index 07a2db5348..317c31e9f0 100644 --- a/assets/icons/arrow_left_16.svg +++ b/assets/icons/arrow_left_16.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/arrow_left_8.svg b/assets/icons/arrow_left_8.svg index a6a7079dd5..e2071d55eb 100644 --- a/assets/icons/arrow_left_8.svg +++ b/assets/icons/arrow_left_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_right_12.svg b/assets/icons/arrow_right_12.svg index 00dc3918fc..c5f70a4958 100644 --- a/assets/icons/arrow_right_12.svg +++ b/assets/icons/arrow_right_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_right_8.svg b/assets/icons/arrow_right_8.svg index ef28fc81e5..fb3f836ef0 100644 --- a/assets/icons/arrow_right_8.svg +++ b/assets/icons/arrow_right_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_up_12.svg b/assets/icons/arrow_up_12.svg index ae4cf92899..c9f35d868b 100644 --- a/assets/icons/arrow_up_12.svg +++ b/assets/icons/arrow_up_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_up_8.svg b/assets/icons/arrow_up_8.svg index 9d3f76a894..0a1e2c44bf 100644 --- a/assets/icons/arrow_up_8.svg +++ b/assets/icons/arrow_up_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/arrow_up_right_8.svg b/assets/icons/arrow_up_right_8.svg index c53175ea6b..3712b31ebd 100644 --- a/assets/icons/arrow_up_right_8.svg +++ b/assets/icons/arrow_up_right_8.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/bolt_12.svg b/assets/icons/bolt_12.svg index bf529dec33..0125c733e2 100644 --- a/assets/icons/bolt_12.svg +++ b/assets/icons/bolt_12.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/bolt_8.svg b/assets/icons/bolt_8.svg index 2e431a0df1..543e72adf8 100644 --- a/assets/icons/bolt_8.svg +++ b/assets/icons/bolt_8.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/bolt_slash_12.svg b/assets/icons/bolt_slash_12.svg index c5d4a1c828..80d99be616 100644 --- a/assets/icons/bolt_slash_12.svg +++ b/assets/icons/bolt_slash_12.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/bolt_slash_8.svg b/assets/icons/bolt_slash_8.svg index 14a22ab8cd..3781a91299 100644 --- a/assets/icons/bolt_slash_8.svg +++ b/assets/icons/bolt_slash_8.svg @@ -1,9 +1,9 @@ - - + + - + diff --git a/assets/icons/caret_down_8.svg b/assets/icons/caret_down_8.svg index 10bd20eb7f..932376d6f8 100644 --- a/assets/icons/caret_down_8.svg +++ b/assets/icons/caret_down_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/caret_left_8.svg b/assets/icons/caret_left_8.svg index c02a5f3468..1b04877a31 100644 --- a/assets/icons/caret_left_8.svg +++ b/assets/icons/caret_left_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/caret_right_8.svg b/assets/icons/caret_right_8.svg index 9a3f2166ef..d1350ee809 100644 --- a/assets/icons/caret_right_8.svg +++ b/assets/icons/caret_right_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/caret_up_8.svg b/assets/icons/caret_up_8.svg index 24c2db5129..bf79244d7d 100644 --- a/assets/icons/caret_up_8.svg +++ b/assets/icons/caret_up_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/chevron_right_8.svg b/assets/icons/chevron_right_8.svg index 64910c54e7..7349274681 100644 --- a/assets/icons/chevron_right_8.svg +++ b/assets/icons/chevron_right_8.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/circle_check_12.svg b/assets/icons/circle_check_12.svg index 0fbb051b84..cb28c8a051 100644 --- a/assets/icons/circle_check_12.svg +++ b/assets/icons/circle_check_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_check_8.svg b/assets/icons/circle_check_8.svg index b4d4d9e624..c4150f058c 100644 --- a/assets/icons/circle_check_8.svg +++ b/assets/icons/circle_check_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_info_12.svg b/assets/icons/circle_info_12.svg index eec07e0abf..26a569737d 100644 --- a/assets/icons/circle_info_12.svg +++ b/assets/icons/circle_info_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_info_8.svg b/assets/icons/circle_info_8.svg index 1d7ac77dd2..49bb03224d 100644 --- a/assets/icons/circle_info_8.svg +++ b/assets/icons/circle_info_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_up_12.svg b/assets/icons/circle_up_12.svg index c4b9677289..4236037fbd 100644 --- a/assets/icons/circle_up_12.svg +++ b/assets/icons/circle_up_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_up_8.svg b/assets/icons/circle_up_8.svg index 17abd30214..e08e0ad492 100644 --- a/assets/icons/circle_up_8.svg +++ b/assets/icons/circle_up_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_x_mark_12.svg b/assets/icons/circle_x_mark_12.svg index c648431dd4..5f11a71ece 100644 --- a/assets/icons/circle_x_mark_12.svg +++ b/assets/icons/circle_x_mark_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/circle_x_mark_8.svg b/assets/icons/circle_x_mark_8.svg index f1323b9589..a0acfc3899 100644 --- a/assets/icons/circle_x_mark_8.svg +++ b/assets/icons/circle_x_mark_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/cloud_slash_12.svg b/assets/icons/cloud_slash_12.svg index adb60d2c27..37d0ee904c 100644 --- a/assets/icons/cloud_slash_12.svg +++ b/assets/icons/cloud_slash_12.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/cloud_slash_8.svg b/assets/icons/cloud_slash_8.svg index 9b7f0011b2..785ded0683 100644 --- a/assets/icons/cloud_slash_8.svg +++ b/assets/icons/cloud_slash_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/dock_bottom_12.svg b/assets/icons/dock_bottom_12.svg index 865162897f..a8099443be 100644 --- a/assets/icons/dock_bottom_12.svg +++ b/assets/icons/dock_bottom_12.svg @@ -1,11 +1,10 @@ - - - - + + + - + diff --git a/assets/icons/dock_bottom_8.svg b/assets/icons/dock_bottom_8.svg index 96fff05511..005ac423ad 100644 --- a/assets/icons/dock_bottom_8.svg +++ b/assets/icons/dock_bottom_8.svg @@ -1,7 +1,7 @@ - - + + diff --git a/assets/icons/dock_full_12.svg b/assets/icons/dock_full_12.svg deleted file mode 100644 index adcf970b07..0000000000 --- a/assets/icons/dock_full_12.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/icons/dock_full_8.svg b/assets/icons/dock_full_8.svg deleted file mode 100644 index d38f599362..0000000000 --- a/assets/icons/dock_full_8.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/icons/dock_modal_12.svg b/assets/icons/dock_modal_12.svg index 2718040987..792baee49c 100644 --- a/assets/icons/dock_modal_12.svg +++ b/assets/icons/dock_modal_12.svg @@ -1,11 +1,10 @@ - - - - + + + - + diff --git a/assets/icons/dock_modal_8.svg b/assets/icons/dock_modal_8.svg index 778b899217..c6f4039004 100644 --- a/assets/icons/dock_modal_8.svg +++ b/assets/icons/dock_modal_8.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/dock_right_12.svg b/assets/icons/dock_right_12.svg index f3ac095405..84cc1a0c2b 100644 --- a/assets/icons/dock_right_12.svg +++ b/assets/icons/dock_right_12.svg @@ -1,11 +1,10 @@ - - - - + + + - + diff --git a/assets/icons/dock_right_8.svg b/assets/icons/dock_right_8.svg index e19bc45cc7..842f41bc8c 100644 --- a/assets/icons/dock_right_8.svg +++ b/assets/icons/dock_right_8.svg @@ -1,7 +1,7 @@ - - + + diff --git a/assets/icons/folder_tree_12.svg b/assets/icons/folder_tree_12.svg index f11253aa4a..580514f74d 100644 --- a/assets/icons/folder_tree_12.svg +++ b/assets/icons/folder_tree_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/folder_tree_8.svg b/assets/icons/folder_tree_8.svg index 758080a611..07ac18e19f 100644 --- a/assets/icons/folder_tree_8.svg +++ b/assets/icons/folder_tree_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/git_diff_8.svg b/assets/icons/git_diff_8.svg index 8f036de090..64290de860 100644 --- a/assets/icons/git_diff_8.svg +++ b/assets/icons/git_diff_8.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/magnifying_glass_12.svg b/assets/icons/magnifying_glass_12.svg index 0ee5e24b24..b9ac5d35b2 100644 --- a/assets/icons/magnifying_glass_12.svg +++ b/assets/icons/magnifying_glass_12.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/magnifying_glass_8.svg b/assets/icons/magnifying_glass_8.svg index 958614203c..d0deb1cdba 100644 --- a/assets/icons/magnifying_glass_8.svg +++ b/assets/icons/magnifying_glass_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/maximize_8.svg b/assets/icons/maximize_8.svg index 72fbc95421..76d29a9d22 100644 --- a/assets/icons/maximize_8.svg +++ b/assets/icons/maximize_8.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/minimize_8.svg b/assets/icons/minimize_8.svg index 58a45811e4..b511cbd355 100644 --- a/assets/icons/minimize_8.svg +++ b/assets/icons/minimize_8.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/plus_12.svg b/assets/icons/plus_12.svg index 7997c97c06..f1770fa248 100644 --- a/assets/icons/plus_12.svg +++ b/assets/icons/plus_12.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/plus_16.svg b/assets/icons/plus_16.svg index 1fa008ac64..c595cf597a 100644 --- a/assets/icons/plus_16.svg +++ b/assets/icons/plus_16.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/plus_8.svg b/assets/icons/plus_8.svg index 9abfd46744..72efa1574e 100644 --- a/assets/icons/plus_8.svg +++ b/assets/icons/plus_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/split_12.svg b/assets/icons/split_12.svg index 8aeb3bcb4c..e4cf1921fa 100644 --- a/assets/icons/split_12.svg +++ b/assets/icons/split_12.svg @@ -1,5 +1,12 @@ + - + + + + + + + diff --git a/assets/icons/terminal_12.svg b/assets/icons/terminal_12.svg index 4c44e657e8..9d5a9447b5 100644 --- a/assets/icons/terminal_12.svg +++ b/assets/icons/terminal_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/terminal_8.svg b/assets/icons/terminal_8.svg index f3e26f12f9..b09495dcf9 100644 --- a/assets/icons/terminal_8.svg +++ b/assets/icons/terminal_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/triangle_exclamation_12.svg b/assets/icons/triangle_exclamation_12.svg index b06707e924..f87d365bdf 100644 --- a/assets/icons/triangle_exclamation_12.svg +++ b/assets/icons/triangle_exclamation_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/triangle_exclamation_8.svg b/assets/icons/triangle_exclamation_8.svg index 166a62e532..96f11015b1 100644 --- a/assets/icons/triangle_exclamation_8.svg +++ b/assets/icons/triangle_exclamation_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/unlock_8.svg b/assets/icons/unlock_8.svg index d358182795..7a40f94345 100644 --- a/assets/icons/unlock_8.svg +++ b/assets/icons/unlock_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/user_circle_12.svg b/assets/icons/user_circle_12.svg index eec0954ba0..8631c36fd6 100644 --- a/assets/icons/user_circle_12.svg +++ b/assets/icons/user_circle_12.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/user_circle_8.svg b/assets/icons/user_circle_8.svg index 7c884eec39..304001d546 100644 --- a/assets/icons/user_circle_8.svg +++ b/assets/icons/user_circle_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/user_group_12.svg b/assets/icons/user_group_12.svg index 13ed0b12e4..5eae1d55b7 100644 --- a/assets/icons/user_group_12.svg +++ b/assets/icons/user_group_12.svg @@ -1,3 +1,3 @@ - + diff --git a/assets/icons/user_group_8.svg b/assets/icons/user_group_8.svg index 9cf29f6e15..69d08b0d3b 100644 --- a/assets/icons/user_group_8.svg +++ b/assets/icons/user_group_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/user_plus_8.svg b/assets/icons/user_plus_8.svg index 6b7179a139..100b43af86 100644 --- a/assets/icons/user_plus_8.svg +++ b/assets/icons/user_plus_8.svg @@ -1,3 +1,10 @@ + + + + + + + diff --git a/assets/icons/x_mark_12.svg b/assets/icons/x_mark_12.svg index aaa97312ac..1c95f979d0 100644 --- a/assets/icons/x_mark_12.svg +++ b/assets/icons/x_mark_12.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/x_mark_16.svg b/assets/icons/x_mark_16.svg index a33adcc403..21a7f1c210 100644 --- a/assets/icons/x_mark_16.svg +++ b/assets/icons/x_mark_16.svg @@ -1,3 +1,10 @@ - - + + + + + + + + + diff --git a/assets/icons/x_mark_8.svg b/assets/icons/x_mark_8.svg index ef81658d61..f724b1515e 100644 --- a/assets/icons/x_mark_8.svg +++ b/assets/icons/x_mark_8.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/assets/icons/x_mark_thin_8.svg b/assets/icons/x_mark_thin_8.svg deleted file mode 100644 index e5aa1b36e3..0000000000 --- a/assets/icons/x_mark_thin_8.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/crates/assets/build.rs b/crates/assets/build.rs new file mode 100644 index 0000000000..8500b24622 --- /dev/null +++ b/crates/assets/build.rs @@ -0,0 +1,29 @@ +use std::process::Command; + +fn main() { + let output = Command::new("npm") + .current_dir("../../styles") + .args(["install", "--no-save"]) + .output() + .expect("failed to run npm"); + if !output.status.success() { + panic!( + "failed to install theme dependencies {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + let output = Command::new("npm") + .current_dir("../../styles") + .args(["run", "build"]) + .output() + .expect("failed to run npm"); + if !output.status.success() { + panic!( + "build script failed {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + println!("cargo:rerun-if-changed=../../styles/src"); +} diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 83dce7c9fe..d73523c8bd 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -218,11 +218,14 @@ impl AutoUpdater { let temp_dir = tempdir::TempDir::new("zed-auto-update")?; let dmg_path = temp_dir.path().join("Zed.dmg"); let mount_path = temp_dir.path().join("Zed"); - let mut mounted_app_path: OsString = mount_path.join("Zed.app").into(); - mounted_app_path.push("/"); let running_app_path = ZED_APP_PATH .clone() .map_or_else(|| cx.platform().app_path(), Ok)?; + let running_app_filename = running_app_path + .file_name() + .ok_or_else(|| anyhow!("invalid running app path"))?; + let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into(); + mounted_app_path.push("/"); let mut dmg_file = File::create(&dmg_path).await?; let mut response = client.get(&release.url, Default::default(), true).await?; diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index bbc9b0ea7f..133a197f15 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -5,7 +5,7 @@ use gpui::{ Element, Entity, MouseButton, View, ViewContext, }; use menu::Cancel; -use settings::Settings; +use settings::{ReleaseChannel, Settings}; use workspace::Notification; pub struct UpdateNotification { @@ -29,13 +29,15 @@ impl View for UpdateNotification { let theme = cx.global::().theme.clone(); let theme = &theme.update_notification; + let app_name = cx.global::().name(); + MouseEventHandler::::new(0, cx, |state, cx| { Flex::column() .with_child( Flex::row() .with_child( Text::new( - format!("Updated to Zed {}", self.version), + format!("Updated to {app_name} {}", self.version), theme.message.text.clone(), ) .contained() @@ -49,7 +51,7 @@ impl View for UpdateNotification { .with_child( MouseEventHandler::::new(0, cx, |state, _| { let style = theme.dismiss_button.style_for(state, false); - Svg::new("icons/x_mark_thin_8.svg") + Svg::new("icons/x_mark_8.svg") .with_color(style.color) .constrained() .with_width(style.icon_width) diff --git a/crates/client/src/amplitude_telemetry.rs b/crates/client/src/amplitude_telemetry.rs deleted file mode 100644 index 5db2bedf03..0000000000 --- a/crates/client/src/amplitude_telemetry.rs +++ /dev/null @@ -1,277 +0,0 @@ -use crate::http::HttpClient; -use db::Db; -use gpui::{ - executor::Background, - serde_json::{self, value::Map, Value}, - AppContext, Task, -}; -use isahc::Request; -use lazy_static::lazy_static; -use parking_lot::Mutex; -use serde::Serialize; -use serde_json::json; -use std::{ - io::Write, - mem, - path::PathBuf, - sync::Arc, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; -use tempfile::NamedTempFile; -use util::{post_inc, ResultExt, TryFutureExt}; -use uuid::Uuid; - -pub struct AmplitudeTelemetry { - http_client: Arc, - executor: Arc, - session_id: u128, - state: Mutex, -} - -#[derive(Default)] -struct AmplitudeTelemetryState { - metrics_id: Option>, - device_id: Option>, - app_version: Option>, - os_version: Option>, - os_name: &'static str, - queue: Vec, - next_event_id: usize, - flush_task: Option>, - log_file: Option, -} - -const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch"; - -lazy_static! { - static ref AMPLITUDE_API_KEY: Option = std::env::var("ZED_AMPLITUDE_API_KEY") - .ok() - .or_else(|| option_env!("ZED_AMPLITUDE_API_KEY").map(|key| key.to_string())); -} - -#[derive(Serialize)] -struct AmplitudeEventBatch { - api_key: &'static str, - events: Vec, -} - -#[derive(Serialize)] -struct AmplitudeEvent { - #[serde(skip_serializing_if = "Option::is_none")] - user_id: Option>, - device_id: Option>, - event_type: String, - #[serde(skip_serializing_if = "Option::is_none")] - event_properties: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - user_properties: Option>, - os_name: &'static str, - os_version: Option>, - app_version: Option>, - platform: &'static str, - event_id: usize, - session_id: u128, - time: u128, -} - -#[cfg(debug_assertions)] -const MAX_QUEUE_LEN: usize = 1; - -#[cfg(not(debug_assertions))] -const MAX_QUEUE_LEN: usize = 10; - -#[cfg(debug_assertions)] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); - -#[cfg(not(debug_assertions))] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30); - -impl AmplitudeTelemetry { - pub fn new(client: Arc, cx: &AppContext) -> Arc { - let platform = cx.platform(); - let this = Arc::new(Self { - http_client: client, - executor: cx.background().clone(), - session_id: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), - state: Mutex::new(AmplitudeTelemetryState { - os_version: platform - .os_version() - .log_err() - .map(|v| v.to_string().into()), - os_name: platform.os_name().into(), - app_version: platform - .app_version() - .log_err() - .map(|v| v.to_string().into()), - device_id: None, - queue: Default::default(), - flush_task: Default::default(), - next_event_id: 0, - log_file: None, - metrics_id: None, - }), - }); - - if AMPLITUDE_API_KEY.is_some() { - this.executor - .spawn({ - let this = this.clone(); - async move { - if let Some(tempfile) = NamedTempFile::new().log_err() { - this.state.lock().log_file = Some(tempfile); - } - } - }) - .detach(); - } - - this - } - - pub fn log_file_path(&self) -> Option { - Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) - } - - pub fn start(self: &Arc, db: Db) { - let this = self.clone(); - self.executor - .spawn( - async move { - let device_id = if let Ok(Some(device_id)) = db.read_kvp("device_id") { - device_id - } else { - let device_id = Uuid::new_v4().to_string(); - db.write_kvp("device_id", &device_id)?; - device_id - }; - - let device_id = Some(Arc::from(device_id)); - let mut state = this.state.lock(); - state.device_id = device_id.clone(); - for event in &mut state.queue { - event.device_id = device_id.clone(); - } - if !state.queue.is_empty() { - drop(state); - this.flush(); - } - - anyhow::Ok(()) - } - .log_err(), - ) - .detach(); - } - - pub fn set_authenticated_user_info( - self: &Arc, - metrics_id: Option, - is_staff: bool, - ) { - let is_signed_in = metrics_id.is_some(); - self.state.lock().metrics_id = metrics_id.map(|s| s.into()); - if is_signed_in { - self.report_event_with_user_properties( - "$identify", - Default::default(), - json!({ "$set": { "staff": is_staff } }), - ) - } - } - - pub fn report_event(self: &Arc, kind: &str, properties: Value) { - self.report_event_with_user_properties(kind, properties, Default::default()); - } - - fn report_event_with_user_properties( - self: &Arc, - kind: &str, - properties: Value, - user_properties: Value, - ) { - if AMPLITUDE_API_KEY.is_none() { - return; - } - - let mut state = self.state.lock(); - let event = AmplitudeEvent { - event_type: kind.to_string(), - time: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), - session_id: self.session_id, - event_properties: if let Value::Object(properties) = properties { - Some(properties) - } else { - None - }, - user_properties: if let Value::Object(user_properties) = user_properties { - Some(user_properties) - } else { - None - }, - user_id: state.metrics_id.clone(), - device_id: state.device_id.clone(), - os_name: state.os_name, - platform: "Zed", - os_version: state.os_version.clone(), - app_version: state.app_version.clone(), - event_id: post_inc(&mut state.next_event_id), - }; - state.queue.push(event); - if state.device_id.is_some() { - if state.queue.len() >= MAX_QUEUE_LEN { - drop(state); - self.flush(); - } else { - let this = self.clone(); - let executor = self.executor.clone(); - state.flush_task = Some(self.executor.spawn(async move { - executor.timer(DEBOUNCE_INTERVAL).await; - this.flush(); - })); - } - } - } - - fn flush(self: &Arc) { - let mut state = self.state.lock(); - let events = mem::take(&mut state.queue); - state.flush_task.take(); - drop(state); - - if let Some(api_key) = AMPLITUDE_API_KEY.as_ref() { - let this = self.clone(); - self.executor - .spawn( - async move { - let mut json_bytes = Vec::new(); - - if let Some(file) = &mut this.state.lock().log_file { - let file = file.as_file_mut(); - for event in &events { - json_bytes.clear(); - serde_json::to_writer(&mut json_bytes, event)?; - file.write_all(&json_bytes)?; - file.write(b"\n")?; - } - } - - let batch = AmplitudeEventBatch { api_key, events }; - json_bytes.clear(); - serde_json::to_writer(&mut json_bytes, &batch)?; - let request = - Request::post(AMPLITUDE_EVENTS_URL).body(json_bytes.into())?; - this.http_client.send(request).await?; - Ok(()) - } - .log_err(), - ) - .detach(); - } - } -} diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index c4a3167f83..c55d5f1681 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1,13 +1,11 @@ #[cfg(any(test, feature = "test-support"))] pub mod test; -pub mod amplitude_telemetry; pub mod channel; pub mod http; pub mod telemetry; pub mod user; -use amplitude_telemetry::AmplitudeTelemetry; use anyhow::{anyhow, Context, Result}; use async_recursion::async_recursion; use async_tungstenite::tungstenite::{ @@ -85,7 +83,6 @@ pub struct Client { peer: Arc, http: Arc, telemetry: Arc, - amplitude_telemetry: Arc, state: RwLock, #[allow(clippy::type_complexity)] @@ -265,7 +262,6 @@ impl Client { id: 0, peer: Peer::new(), telemetry: Telemetry::new(http.clone(), cx), - amplitude_telemetry: AmplitudeTelemetry::new(http.clone(), cx), http, state: Default::default(), @@ -378,8 +374,6 @@ impl Client { } Status::SignedOut | Status::UpgradeRequired => { self.telemetry.set_authenticated_user_info(None, false); - self.amplitude_telemetry - .set_authenticated_user_info(None, false); state._reconnect_task.take(); } _ => {} @@ -1029,7 +1023,6 @@ impl Client { let platform = cx.platform(); let executor = cx.background(); let telemetry = self.telemetry.clone(); - let amplitude_telemetry = self.amplitude_telemetry.clone(); let http = self.http.clone(); executor.clone().spawn(async move { // Generate a pair of asymmetric encryption keys. The public key will be used by the @@ -1114,7 +1107,6 @@ impl Client { platform.activate(true); telemetry.report_event("authenticate with browser", Default::default()); - amplitude_telemetry.report_event("authenticate with browser", Default::default()); Ok(Credentials { user_id: user_id.parse()?, @@ -1227,16 +1219,13 @@ impl Client { pub fn start_telemetry(&self, db: Db) { self.telemetry.start(db.clone()); - self.amplitude_telemetry.start(db); } pub fn report_event(&self, kind: &str, properties: Value) { self.telemetry.report_event(kind, properties.clone()); - self.amplitude_telemetry.report_event(kind, properties); } pub fn telemetry_log_file_path(&self) -> Option { - self.amplitude_telemetry.log_file_path(); self.telemetry.log_file_path() } } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 02c1790664..f8e7d161c3 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; use serde_json::json; +use settings::ReleaseChannel; use std::{ io::Write, mem, @@ -32,6 +33,7 @@ struct TelemetryState { metrics_id: Option>, device_id: Option>, app_version: Option>, + release_channel: Option<&'static str>, os_version: Option>, os_name: &'static str, queue: Vec, @@ -67,11 +69,16 @@ struct MixpanelEventProperties { // Custom fields #[serde(skip_serializing_if = "Option::is_none", flatten)] event_properties: Option>, + #[serde(rename = "OS Name")] os_name: &'static str, + #[serde(rename = "OS Version")] os_version: Option>, + #[serde(rename = "Release Channel")] + release_channel: Option<&'static str>, + #[serde(rename = "App Version")] app_version: Option>, + #[serde(rename = "Signed In")] signed_in: bool, - platform: &'static str, } #[derive(Serialize)] @@ -99,19 +106,19 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30); impl Telemetry { pub fn new(client: Arc, cx: &AppContext) -> Arc { let platform = cx.platform(); + let release_channel = if cx.has_global::() { + Some(cx.global::().name()) + } else { + None + }; let this = Arc::new(Self { http_client: client, executor: cx.background().clone(), state: Mutex::new(TelemetryState { - os_version: platform - .os_version() - .log_err() - .map(|v| v.to_string().into()), + os_version: platform.os_version().ok().map(|v| v.to_string().into()), os_name: platform.os_name().into(), - app_version: platform - .app_version() - .log_err() - .map(|v| v.to_string().into()), + app_version: platform.app_version().ok().map(|v| v.to_string().into()), + release_channel, device_id: None, metrics_id: None, queue: Default::default(), @@ -194,7 +201,11 @@ impl Telemetry { let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest { token, distinct_id: device_id, - set: json!({ "staff": is_staff, "id": metrics_id }), + set: json!({ + "Staff": is_staff, + "ID": metrics_id, + "App": true + }), }])?; let request = Request::post(MIXPANEL_ENGAGE_URL) .header("Content-Type", "application/json") @@ -227,9 +238,9 @@ impl Telemetry { }, os_name: state.os_name, os_version: state.os_version.clone(), + release_channel: state.release_channel, app_version: state.app_version.clone(), signed_in: state.metrics_id.is_some(), - platform: "Zed", }, }; state.queue.push(event); diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index d06a6682c5..11b9ef6117 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -146,21 +146,11 @@ impl UserStore { Some(info.metrics_id.clone()), info.staff, ); - client.amplitude_telemetry.set_authenticated_user_info( - Some(info.metrics_id), - info.staff, - ); } else { client.telemetry.set_authenticated_user_info(None, false); - client - .amplitude_telemetry - .set_authenticated_user_info(None, false); } client.telemetry.report_event("sign in", Default::default()); - client - .amplitude_telemetry - .report_event("sign in", Default::default()); current_user_tx.send(user).await.ok(); } } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 658ca26425..ab414e051b 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -54,6 +54,7 @@ impl View for CollabTitlebarItem { let theme = cx.global::().theme.clone(); let mut container = Flex::row(); + container.add_children(self.render_toggle_screen_sharing_button(&theme, cx)); if workspace.read(cx).client().status().borrow().is_connected() { @@ -324,6 +325,8 @@ impl CollabTitlebarItem { cx, ) .aligned() + .contained() + .with_margin_left(theme.workspace.titlebar.avatar_margin) .boxed() } diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index dcb9894006..1e0574de95 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -52,7 +52,7 @@ pub fn render_user_notification( .with_child( MouseEventHandler::::new(user.id as usize, cx, |state, _| { let style = theme.dismiss_button.style_for(state, false); - Svg::new("icons/x_mark_thin_8.svg") + Svg::new("icons/x_mark_8.svg") .with_color(style.color) .constrained() .with_width(style.icon_width) diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 1eeac03375..e0b932003e 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -18,7 +18,7 @@ lazy_static = "1.4.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" rusqlite = { version = "0.28.0", features = ["bundled", "serde_json"] } -rusqlite_migration = "1.0.0" +rusqlite_migration = { git = "https://github.com/cljoly/rusqlite_migration", rev = "c433555d7c1b41b103426e35756eb3144d0ebbc6" } serde = { workspace = true } serde_rusqlite = "0.31.0" diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 06776832b5..2949acdb83 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -25,9 +25,9 @@ pub struct RealDb { impl Db { /// Open or create a database at the given directory path. - pub fn open(db_dir: &Path) -> Self { + pub fn open(db_dir: &Path, channel: &'static str) -> Self { // Use 0 for now. Will implement incrementing and clearing of old db files soon TM - let current_db_dir = db_dir.join(Path::new("0")); + let current_db_dir = db_dir.join(Path::new(&format!("0-{}", channel))); fs::create_dir_all(¤t_db_dir) .expect("Should be able to create the database directory"); let db_path = current_db_dir.join(Path::new("db.sqlite")); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4409fa17ad..015339e4d3 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -100,7 +100,7 @@ impl View for ProjectDiagnosticsEditor { } fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { - if !self.path_states.is_empty() { + if cx.is_self_focused() && !self.path_states.is_empty() { cx.focus(&self.editor); } } diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index df66ec2125..681692f0f5 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -71,7 +71,6 @@ impl BlinkManager { if epoch == self.blink_epoch && self.enabled && !self.blinking_paused { self.visible = !self.visible; cx.notify(); - dbg!(cx.handle()); let epoch = self.next_blink_epoch(); let interval = self.blink_interval; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 32e58a5d82..cb6b569223 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1353,6 +1353,7 @@ impl Editor { ) { self.scroll_top_anchor = anchor; self.scroll_position = position; + self.make_scrollbar_visible(cx); cx.emit(Event::ScrollPositionChanged { local: false }); cx.notify(); } @@ -6395,7 +6396,7 @@ impl Editor { project.read(cx).client().report_event( name, json!({ - "file_extension": file + "File Extension": file .path() .extension() .and_then(|e| e.to_str()) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ccae9db92b..5f26ddb3b8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1566,7 +1566,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) { +async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx); let language = Arc::new( Language::new( @@ -1623,6 +1623,43 @@ async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) { "}); } +#[gpui::test] +async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) { + let mut cx = EditorTestContext::new(cx); + let language = Arc::new( + Language::new( + LanguageConfig::default(), + Some(tree_sitter_rust::language()), + ) + .with_indents_query(r#"(_ "{" "}" @end) @indent"#) + .unwrap(), + ); + cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx)); + + cx.update(|cx| { + cx.update_global::(|settings, _| { + settings.editor_overrides.tab_size = Some(4.try_into().unwrap()); + }); + }); + + cx.set_state(indoc! {" + fn a() { + if b { + \t ˇc + } + } + "}); + + cx.update_editor(|e, cx| e.tab(&Tab, cx)); + cx.assert_editor_state(indoc! {" + fn a() { + if b { + ˇc + } + } + "}); +} + #[gpui::test] async fn test_indent_outdent(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx); diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 999f410db5..2432448ed5 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -678,6 +678,19 @@ impl<'a> MutableSelectionsCollection<'a> { }); } + pub fn maybe_move_cursors_with( + &mut self, + mut update_cursor_position: impl FnMut( + &DisplaySnapshot, + DisplayPoint, + SelectionGoal, + ) -> Option<(DisplayPoint, SelectionGoal)>, + ) { + self.move_cursors_with(|map, point, goal| { + update_cursor_position(map, point, goal).unwrap_or((point, goal)) + }) + } + pub fn replace_cursors_with( &mut self, mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 925a4542aa..c62305f572 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1544,17 +1544,18 @@ impl MutableAppContext { { MatchResult::None => false, MatchResult::Pending => true, - MatchResult::Match { view_id, action } => { - if self.handle_dispatch_action_from_effect( - window_id, - Some(view_id), - action.as_ref(), - ) { - self.keystroke_matcher.clear_pending(); - true - } else { - false + MatchResult::Matches(matches) => { + for (view_id, action) in matches { + if self.handle_dispatch_action_from_effect( + window_id, + Some(view_id), + action.as_ref(), + ) { + self.keystroke_matcher.clear_pending(); + return true; + } } + false } } } else { diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 603190a810..fc97f69624 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -13,16 +13,11 @@ extern "C" { } pub struct Matcher { - pending: HashMap, + pending_views: HashMap, + pending_keystrokes: Vec, keymap: Keymap, } -#[derive(Default)] -struct Pending { - keystrokes: Vec, - context: Option, -} - #[derive(Default)] pub struct Keymap { bindings: Vec, @@ -77,21 +72,21 @@ where pub enum MatchResult { None, Pending, - Match { - view_id: usize, - action: Box, - }, + Matches(Vec<(usize, Box)>), } impl Debug for MatchResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - MatchResult::None => f.debug_struct("MatchResult2::None").finish(), - MatchResult::Pending => f.debug_struct("MatchResult2::Pending").finish(), - MatchResult::Match { view_id, action } => f - .debug_struct("MatchResult::Match") - .field("view_id", view_id) - .field("action", &action.name()) + MatchResult::None => f.debug_struct("MatchResult::None").finish(), + MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(), + MatchResult::Matches(matches) => f + .debug_list() + .entries( + matches + .iter() + .map(|(view_id, action)| format!("{view_id}, {}", action.name())), + ) .finish(), } } @@ -102,13 +97,14 @@ impl PartialEq for MatchResult { match (self, other) { (MatchResult::None, MatchResult::None) => true, (MatchResult::Pending, MatchResult::Pending) => true, - ( - MatchResult::Match { view_id, action }, - MatchResult::Match { - view_id: other_view_id, - action: other_action, - }, - ) => view_id == other_view_id && action.eq(other_action.as_ref()), + (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => { + matches.len() == other_matches.len() + && matches.iter().zip(other_matches.iter()).all( + |((view_id, action), (other_view_id, other_action))| { + view_id == other_view_id && action.eq(other_action.as_ref()) + }, + ) + } _ => false, } } @@ -119,23 +115,24 @@ impl Eq for MatchResult {} impl Matcher { pub fn new(keymap: Keymap) -> Self { Self { - pending: HashMap::new(), + pending_views: HashMap::new(), + pending_keystrokes: Vec::new(), keymap, } } pub fn set_keymap(&mut self, keymap: Keymap) { - self.pending.clear(); + self.clear_pending(); self.keymap = keymap; } pub fn add_bindings>(&mut self, bindings: T) { - self.pending.clear(); + self.clear_pending(); self.keymap.add_bindings(bindings); } pub fn clear_bindings(&mut self) { - self.pending.clear(); + self.clear_pending(); self.keymap.clear(); } @@ -144,11 +141,12 @@ impl Matcher { } pub fn clear_pending(&mut self) { - self.pending.clear(); + self.pending_keystrokes.clear(); + self.pending_views.clear(); } pub fn has_pending_keystrokes(&self) -> bool { - !self.pending.is_empty() + !self.pending_keystrokes.is_empty() } pub fn push_keystroke( @@ -157,53 +155,53 @@ impl Matcher { dispatch_path: Vec<(usize, Context)>, ) -> MatchResult { let mut any_pending = false; + let mut matched_bindings = Vec::new(); + + let first_keystroke = self.pending_keystrokes.is_empty(); + self.pending_keystrokes.push(keystroke); - let first_keystroke = self.pending.is_empty(); for (view_id, context) in dispatch_path { - if !first_keystroke && !self.pending.contains_key(&view_id) { + // Don't require pending view entry if there are no pending keystrokes + if !first_keystroke && !self.pending_views.contains_key(&view_id) { continue; } - let pending = self.pending.entry(view_id).or_default(); - - if let Some(pending_context) = pending.context.as_ref() { - if pending_context != &context { - pending.keystrokes.clear(); + // If there is a previous view context, invalidate that view if it + // has changed + if let Some(previous_view_context) = self.pending_views.remove(&view_id) { + if previous_view_context != context { + continue; } } - pending.keystrokes.push(keystroke.clone()); - - let mut retain_pending = false; + // Find the bindings which map the pending keystrokes and current context for binding in self.keymap.bindings.iter().rev() { - if binding.keystrokes.starts_with(&pending.keystrokes) + if binding.keystrokes.starts_with(&self.pending_keystrokes) && binding .context_predicate .as_ref() .map(|c| c.eval(&context)) .unwrap_or(true) { - if binding.keystrokes.len() == pending.keystrokes.len() { - self.pending.remove(&view_id); - return MatchResult::Match { - view_id, - action: binding.action.boxed_clone(), - }; + // If the binding is completed, push it onto the matches list + if binding.keystrokes.len() == self.pending_keystrokes.len() { + matched_bindings.push((view_id, binding.action.boxed_clone())); } else { - retain_pending = true; - pending.context = Some(context.clone()); + // Otherwise, the binding is still pending + self.pending_views.insert(view_id, context.clone()); + any_pending = true; } } } - - if retain_pending { - any_pending = true; - } else { - self.pending.remove(&view_id); - } } - if any_pending { + if !any_pending { + self.clear_pending(); + } + + if !matched_bindings.is_empty() { + MatchResult::Matches(matched_bindings) + } else if any_pending { MatchResult::Pending } else { MatchResult::None @@ -499,7 +497,7 @@ mod tests { #[test] fn test_push_keystroke() -> Result<()> { - actions!(test, [B, AB, C]); + actions!(test, [B, AB, C, D, DA]); let mut ctx1 = Context::default(); ctx1.set.insert("1".into()); @@ -513,39 +511,58 @@ mod tests { Binding::new("a b", AB, Some("1")), Binding::new("b", B, Some("2")), Binding::new("c", C, Some("2")), + Binding::new("d", D, Some("1")), + Binding::new("d", D, Some("2")), + Binding::new("d a", DA, Some("2")), ]); let mut matcher = Matcher::new(keymap); + // Binding with pending prefix always takes precedence assert_eq!( + matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), MatchResult::Pending, - matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()) ); + // B alone doesn't match because a was pending, so AB is returned instead assert_eq!( - MatchResult::Match { - view_id: 1, - action: Box::new(AB) - }, - matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()) + matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), + MatchResult::Matches(vec![(1, Box::new(AB))]), ); - assert!(matcher.pending.is_empty()); + assert!(!matcher.has_pending_keystrokes()); + + // Without an a prefix, B is dispatched like expected assert_eq!( - MatchResult::Match { - view_id: 2, - action: Box::new(B) - }, - matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()) + matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), + MatchResult::Matches(vec![(2, Box::new(B))]), ); - assert!(matcher.pending.is_empty()); + assert!(!matcher.has_pending_keystrokes()); + + // If a is prefixed, C will not be dispatched because there + // was a pending binding for it assert_eq!( + matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), MatchResult::Pending, - matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()) ); assert_eq!( + matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), MatchResult::None, - matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()) ); - assert!(matcher.pending.is_empty()); + assert!(!matcher.has_pending_keystrokes()); + + // If a single keystroke matches multiple bindings in the tree + // all of them are returned so that we can fallback if the action + // handler decides to propagate the action + assert_eq!( + matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), + MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), + ); + // If none of the d action handlers consume the binding, a pending + // binding may then be used + assert_eq!( + matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), + MatchResult::Matches(vec![(2, Box::new(DA))]), + ); + assert!(!matcher.has_pending_keystrokes()); Ok(()) } @@ -666,71 +683,60 @@ mod tests { // Basic match assert_eq!( - downcast(&matcher.test_keystroke("a", vec![(1, ctx_a.clone())])), - Some(&A("x".to_string())) + matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_a.clone())]), + MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))]) ); + matcher.clear_pending(); // Multi-keystroke match - assert!(matcher - .test_keystroke("a", vec![(1, ctx_b.clone())]) - .is_none()); assert_eq!( - downcast(&matcher.test_keystroke("b", vec![(1, ctx_b.clone())])), - Some(&Ab) + matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_b.clone())]), + MatchResult::Pending ); + assert_eq!( + matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_b.clone())]), + MatchResult::Matches(vec![(1, Box::new(Ab))]) + ); + matcher.clear_pending(); // Failed matches don't interfere with matching subsequent keys - assert!(matcher - .test_keystroke("x", vec![(1, ctx_a.clone())]) - .is_none()); assert_eq!( - downcast(&matcher.test_keystroke("a", vec![(1, ctx_a.clone())])), - Some(&A("x".to_string())) + matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, ctx_a.clone())]), + MatchResult::None ); + assert_eq!( + matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_a.clone())]), + MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))]) + ); + matcher.clear_pending(); // Pending keystrokes are cleared when the context changes - assert!(&matcher - .test_keystroke("a", vec![(1, ctx_b.clone())]) - .is_none()); assert_eq!( - downcast(&matcher.test_keystroke("b", vec![(1, ctx_a.clone())])), - Some(&B) + matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_b.clone())]), + MatchResult::Pending ); + assert_eq!( + matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_a.clone())]), + MatchResult::None + ); + matcher.clear_pending(); let mut ctx_c = Context::default(); ctx_c.set.insert("c".into()); // Pending keystrokes are maintained per-view - assert!(matcher - .test_keystroke("a", vec![(1, ctx_b.clone()), (2, ctx_c.clone())]) - .is_none()); assert_eq!( - downcast(&matcher.test_keystroke("b", vec![(1, ctx_b.clone())])), - Some(&Ab) + matcher.push_keystroke( + Keystroke::parse("a")?, + vec![(1, ctx_b.clone()), (2, ctx_c.clone())] + ), + MatchResult::Pending + ); + assert_eq!( + matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_b.clone())]), + MatchResult::Matches(vec![(1, Box::new(Ab))]) ); Ok(()) } - - fn downcast(action: &Option>) -> Option<&A> { - action - .as_ref() - .and_then(|action| action.as_any().downcast_ref()) - } - - impl Matcher { - fn test_keystroke( - &mut self, - keystroke: &str, - dispatch_path: Vec<(usize, Context)>, - ) -> Option> { - if let MatchResult::Match { action, .. } = - self.push_keystroke(Keystroke::parse(keystroke).unwrap(), dispatch_path) - { - Some(action.boxed_clone()) - } else { - None - } - } - } } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5a75f1a56f..e99f643e62 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1068,32 +1068,40 @@ impl Buffer { self.edit(edits, None, cx); } + // Create a minimal edit that will cause the the given row to be indented + // with the given size. After applying this edit, the length of the line + // will always be at least `new_size.len`. pub fn edit_for_indent_size_adjustment( row: u32, current_size: IndentSize, new_size: IndentSize, ) -> Option<(Range, String)> { - if new_size.kind != current_size.kind && current_size.len > 0 { - return None; - } + if new_size.kind != current_size.kind { + Some(( + Point::new(row, 0)..Point::new(row, current_size.len), + iter::repeat(new_size.char()) + .take(new_size.len as usize) + .collect::(), + )) + } else { + match new_size.len.cmp(¤t_size.len) { + Ordering::Greater => { + let point = Point::new(row, 0); + Some(( + point..point, + iter::repeat(new_size.char()) + .take((new_size.len - current_size.len) as usize) + .collect::(), + )) + } - match new_size.len.cmp(¤t_size.len) { - Ordering::Greater => { - let point = Point::new(row, 0); - Some(( - point..point, - iter::repeat(new_size.char()) - .take((new_size.len - current_size.len) as usize) - .collect::(), - )) + Ordering::Less => Some(( + Point::new(row, 0)..Point::new(row, current_size.len - new_size.len), + String::new(), + )), + + Ordering::Equal => None, } - - Ordering::Less => Some(( - Point::new(row, 0)..Point::new(row, current_size.len - new_size.len), - String::new(), - )), - - Ordering::Equal => None, } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index b5d1d0caa3..8f5d9b0e55 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1015,15 +1015,17 @@ impl ProjectPanel { let show_editor = details.is_editing && !details.is_processing; MouseEventHandler::::new(entry_id.to_usize(), cx, |state, cx| { let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; - let mut style = theme.entry.style_for(state, details.is_selected).clone(); - if details.is_ignored { - style.text.color.fade_out(theme.ignored_entry_fade); - style.icon_color.fade_out(theme.ignored_entry_fade); - } - if details.is_cut { - style.text.color.fade_out(theme.cut_entry_fade); - style.icon_color.fade_out(theme.cut_entry_fade); - } + + let entry_style = if details.is_cut { + &theme.cut_entry + } else if details.is_ignored { + &theme.ignored_entry + } else { + &theme.entry + }; + + let style = entry_style.style_for(state, details.is_selected).clone(); + let row_container_style = if show_editor { theme.filename_editor.container } else { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index bad35c3271..e801e00757 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -62,6 +62,16 @@ pub enum ReleaseChannel { Stable, } +impl ReleaseChannel { + pub fn name(&self) -> &'static str { + match self { + ReleaseChannel::Dev => "Zed Dev", + ReleaseChannel::Preview => "Zed Preview", + ReleaseChannel::Stable => "Zed", + } + } +} + impl FeatureFlags { pub fn keymap_files(&self) -> Vec<&'static str> { vec![] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9cd378cb52..97c0b0a79d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -326,8 +326,8 @@ pub struct ProjectPanel { #[serde(flatten)] pub container: ContainerStyle, pub entry: Interactive, - pub cut_entry_fade: f32, - pub ignored_entry_fade: f32, + pub ignored_entry: Interactive, + pub cut_entry: Interactive, pub filename_editor: FieldEditor, pub indent_width: f32, } diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 20eb58e1e0..5aa1df6dd8 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -137,6 +137,11 @@ impl Motion { ) } + pub fn infallible(self) -> bool { + use Motion::*; + matches!(self, StartOfDocument | CurrentLine | EndOfDocument) + } + pub fn inclusive(self) -> bool { use Motion::*; match self { @@ -164,9 +169,9 @@ impl Motion { point: DisplayPoint, goal: SelectionGoal, times: usize, - ) -> (DisplayPoint, SelectionGoal) { + ) -> Option<(DisplayPoint, SelectionGoal)> { use Motion::*; - match self { + let (new_point, goal) = match self { Left => (left(map, point, times), SelectionGoal::None), Backspace => (backspace(map, point, times), SelectionGoal::None), Down => down(map, point, goal, times), @@ -191,7 +196,9 @@ impl Motion { StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None), EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None), Matching => (matching(map, point), SelectionGoal::None), - } + }; + + (new_point != point || self.infallible()).then_some((new_point, goal)) } // Expands a selection using self motion for an operator @@ -201,12 +208,13 @@ impl Motion { selection: &mut Selection, times: usize, expand_to_surrounding_newline: bool, - ) { - let (new_head, goal) = self.move_point(map, selection.head(), selection.goal, times); - selection.set_head(new_head, goal); + ) -> bool { + if let Some((new_head, goal)) = + self.move_point(map, selection.head(), selection.goal, times) + { + selection.set_head(new_head, goal); - if self.linewise() { - if selection.start != selection.end { + if self.linewise() { selection.start = map.prev_line_boundary(selection.start.to_point(map)).1; if expand_to_surrounding_newline { @@ -215,7 +223,7 @@ impl Motion { *selection.end.column_mut() = 0; selection.end = map.clip_point(selection.end, Bias::Right); // Don't reset the end here - return; + return true; } else if selection.start.row() > 0 { *selection.start.row_mut() -= 1; *selection.start.column_mut() = map.line_len(selection.start.row()); @@ -224,31 +232,33 @@ impl Motion { } (_, selection.end) = map.next_line_boundary(selection.end.to_point(map)); - } - } else { - // If the motion is exclusive and the end of the motion is in column 1, the - // end of the motion is moved to the end of the previous line and the motion - // becomes inclusive. Example: "}" moves to the first line after a paragraph, - // but "d}" will not include that line. - let mut inclusive = self.inclusive(); - if !inclusive - && self != Motion::Backspace - && selection.end.row() > selection.start.row() - && selection.end.column() == 0 - && selection.end.row() > 0 - { - inclusive = true; - *selection.end.row_mut() -= 1; - *selection.end.column_mut() = 0; - selection.end = map.clip_point( - map.next_line_boundary(selection.end.to_point(map)).1, - Bias::Left, - ); - } + } else { + // If the motion is exclusive and the end of the motion is in column 1, the + // end of the motion is moved to the end of the previous line and the motion + // becomes inclusive. Example: "}" moves to the first line after a paragraph, + // but "d}" will not include that line. + let mut inclusive = self.inclusive(); + if !inclusive + && self != Motion::Backspace + && selection.end.row() > selection.start.row() + && selection.end.column() == 0 + { + inclusive = true; + *selection.end.row_mut() -= 1; + *selection.end.column_mut() = 0; + selection.end = map.clip_point( + map.next_line_boundary(selection.end.to_point(map)).1, + Bias::Left, + ); + } - if inclusive && selection.end.column() < map.line_len(selection.end.row()) { - *selection.end.column_mut() += 1; + if inclusive && selection.end.column() < map.line_len(selection.end.row()) { + *selection.end.column_mut() += 1; + } } + true + } else { + false } } } @@ -256,7 +266,7 @@ impl Motion { fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint { for _ in 0..times { *point.column_mut() = point.column().saturating_sub(1); - point = map.clip_point(point, Bias::Right); + point = map.clip_point(point, Bias::Left); if point.column() == 0 { break; } @@ -325,9 +335,7 @@ pub(crate) fn next_word_start( || at_newline && crossed_newline || at_newline && left == '\n'; // Prevents skipping repeated empty lines - if at_newline { - crossed_newline = true; - } + crossed_newline |= at_newline; found }) } @@ -350,7 +358,7 @@ fn next_word_end( }); // find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know - // we have backtraced already + // we have backtracked already if !map .chars_at(point) .nth(1) diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 4720ed7373..67b58ac585 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -115,7 +115,11 @@ pub fn normal_object(object: Object, cx: &mut MutableAppContext) { fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut MutableAppContext) { vim.update_active_editor(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::Fit), cx, |s| { - s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal, times)) + s.move_cursors_with(|map, cursor, goal| { + motion + .move_point(map, cursor, goal, times) + .unwrap_or((cursor, goal)) + }) }) }); } @@ -125,7 +129,7 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext, times: usize, ignore_punctuation: bool, -) { - if times > 1 { - Motion::NextWordStart { ignore_punctuation }.expand_selection( - map, - selection, - times - 1, - false, - ); +) -> bool { + if times == 1 { + let in_word = map + .chars_at(selection.head()) + .next() + .map(|(c, _)| char_kind(c) != CharKind::Whitespace) + .unwrap_or_default(); + + if in_word { + selection.end = movement::find_boundary(map, selection.end, |left, right| { + let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation); + let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation); + + left_kind != right_kind && left_kind != CharKind::Whitespace + }); + true + } else { + Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, 1, false) + } + } else { + Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, times, false) } - - if times == 1 && selection.end.column() == map.line_len(selection.end.row()) { - return; - } - - selection.end = movement::find_boundary(map, selection.end, |left, right| { - let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation); - - left_kind != right_kind || left == '\n' || right == '\n' - }); } #[cfg(test)] diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index b0ff7b5f0e..d9446e68a1 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -143,7 +143,7 @@ mod test { Test test ˇ test"}, - ExemptionFeatures::DeletionOnEmptyLine, + ExemptionFeatures::DeleteWordOnEmptyLine, ) .await; @@ -169,7 +169,7 @@ mod test { Test test ˇ test"}, - ExemptionFeatures::DeletionOnEmptyLine, + ExemptionFeatures::OperatorLastNewlineRemains, ) .await; diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index c499aafa08..a6bf5bc6fa 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -8,7 +8,10 @@ use util::test::marked_text_offsets; use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext}; use crate::state::Mode; -pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[]; +pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[ + ExemptionFeatures::DeletionOnEmptyLine, + ExemptionFeatures::OperatorAbortsOnFailedMotion, +]; /// Enum representing features we have tests for but which don't work, yet. Used /// to add exemptions and automatically @@ -19,6 +22,10 @@ pub enum ExemptionFeatures { DeletionOnEmptyLine, // When a motion fails, it should should not apply linewise operations OperatorAbortsOnFailedMotion, + // When an operator completes at the end of the file, an extra newline is left + OperatorLastNewlineRemains, + // Deleting a word on an empty line doesn't remove the newline + DeleteWordOnEmptyLine, // OBJECTS // Resulting position after the operation is slightly incorrect for unintuitive reasons. diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 481d2570ae..7e0d48499c 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -30,20 +30,23 @@ pub fn visual_motion(motion: Motion, times: usize, cx: &mut MutableAppContext) { s.move_with(|map, selection| { let was_reversed = selection.reversed; - let (new_head, goal) = - motion.move_point(map, selection.head(), selection.goal, times); - selection.set_head(new_head, goal); + if let Some((new_head, goal)) = + motion.move_point(map, selection.head(), selection.goal, times) + { + selection.set_head(new_head, goal); - if was_reversed && !selection.reversed { - // Head was at the start of the selection, and now is at the end. We need to move the start - // back by one if possible in order to compensate for this change. - *selection.start.column_mut() = selection.start.column().saturating_sub(1); - selection.start = map.clip_point(selection.start, Bias::Left); - } else if !was_reversed && selection.reversed { - // Head was at the end of the selection, and now is at the start. We need to move the end - // forward by one if possible in order to compensate for this change. - *selection.end.column_mut() = selection.end.column() + 1; - selection.end = map.clip_point(selection.end, Bias::Right); + if was_reversed && !selection.reversed { + // Head was at the start of the selection, and now is at the end. We need to move the start + // back by one if possible in order to compensate for this change. + *selection.start.column_mut() = + selection.start.column().saturating_sub(1); + selection.start = map.clip_point(selection.start, Bias::Left); + } else if !was_reversed && selection.reversed { + // Head was at the end of the selection, and now is at the start. We need to move the end + // forward by one if possible in order to compensate for this change. + *selection.end.column_mut() = selection.end.column() + 1; + selection.end = map.clip_point(selection.end, Bias::Right); + } } }); }); diff --git a/crates/vim/test_data/test_change_end_of_document.json b/crates/vim/test_data/test_change_end_of_document.json index dcd810910c..8a0cc840be 100644 --- a/crates/vim/test_data/test_change_end_of_document.json +++ b/crates/vim/test_data/test_change_end_of_document.json @@ -1 +1 @@ -[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_gg.json b/crates/vim/test_data/test_change_gg.json index dceb783e9c..b8e250788c 100644 --- a/crates/vim/test_data/test_change_gg.json +++ b/crates/vim/test_data/test_change_gg.json @@ -1 +1 @@ -[{"Text":"\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nbrown fox\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nbrown fox\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_j.json b/crates/vim/test_data/test_change_j.json index 3057656a99..a8ce6bbc85 100644 --- a/crates/vim/test_data/test_change_j.json +++ b/crates/vim/test_data/test_change_j.json @@ -1 +1 @@ -[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,6],"end":[2,6]}},{"Mode":"Normal"},{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n"},{"Mode":"Normal"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_change_k.json b/crates/vim/test_data/test_change_k.json index 7352c6052b..e37a013ec3 100644 --- a/crates/vim/test_data/test_change_k.json +++ b/crates/vim/test_data/test_change_k.json @@ -1 +1 @@ -[{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_dd.json b/crates/vim/test_data/test_dd.json index 1e579496fa..fa86b9d3b5 100644 --- a/crates/vim/test_data/test_dd.json +++ b/crates/vim/test_data/test_dd.json @@ -1 +1 @@ -[{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"}] \ No newline at end of file +[{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_delete_end_of_document.json b/crates/vim/test_data/test_delete_end_of_document.json index c0900e41b8..a43eb2d6fd 100644 --- a/crates/vim/test_data/test_delete_end_of_document.json +++ b/crates/vim/test_data/test_delete_end_of_document.json @@ -1 +1 @@ -[{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"}] \ No newline at end of file +[{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,5],"end":[2,5]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_delete_gg.json b/crates/vim/test_data/test_delete_gg.json index c33f03057d..ac6e54f355 100644 --- a/crates/vim/test_data/test_delete_gg.json +++ b/crates/vim/test_data/test_delete_gg.json @@ -1 +1 @@ -[{"Text":"jumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}] \ No newline at end of file +[{"Text":"jumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_h_through_unicode.json b/crates/vim/test_data/test_h_through_unicode.json new file mode 100644 index 0000000000..86d6d0832d --- /dev/null +++ b/crates/vim/test_data/test_h_through_unicode.json @@ -0,0 +1 @@ +[{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,3],"end":[0,3]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,13],"end":[0,13]}},{"Mode":"Normal"}] \ No newline at end of file diff --git a/crates/vim/test_data/test_repeated_cb.json b/crates/vim/test_data/test_repeated_cb.json index b00195b2d9..ccb2312091 100644 --- a/crates/vim/test_data/test_repeated_cb.json +++ b/crates/vim/test_data/test_repeated_cb.json @@ -1 +1 @@ -[{"Text":"The ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick \n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\njumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumpsover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-ver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,10],"end":[2,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,10],"end":[2,10]}},{"Mode":"Insert"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The \n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick \nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\njumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\n-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumpsver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The \nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox ver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox \nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\nover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\n\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\nver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file +[{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"The ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick \n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\njumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumpsover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-ver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,10],"end":[2,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,10],"end":[2,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The \n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick \nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\njumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\n-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumpsver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,9],"end":[2,9]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The \nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\n-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox ver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox \nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,4],"end":[2,4]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\nover\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\n\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"ick brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":" brown\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"n\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\n\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nfox jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"jumps-over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The -over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Insert"},{"Text":"The quick over\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Insert"},{"Text":"The quick brown\nver\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick brown\n\nthe lazy dog\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}] \ No newline at end of file diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 651afce1c6..e703922dcb 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1261,7 +1261,7 @@ impl Pane { ConstrainedBox::new(if hovered { let item_id = item.id(); enum TabCloseButton {} - let icon = Svg::new("icons/x_mark_thin_8.svg"); + let icon = Svg::new("icons/x_mark_8.svg"); MouseEventHandler::::new(item_id, cx, |mouse_state, _| { if mouse_state.hovered() { icon.with_color(tab_style.icon_close_active).boxed() @@ -1324,7 +1324,7 @@ impl Pane { // Add the close dock button if this pane is a dock .with_children( self.docked - .map(|_| tab_bar_button(3, "icons/x_mark_thin_8.svg", cx, |_| HideDock)), + .map(|_| tab_bar_button(3, "icons/x_mark_8.svg", cx, |_| HideDock)), ) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 326fac8b87..920e5363bb 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.62.0" +version = "0.62.8" [lib] name = "zed" diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 90012116c0..870bbe4e50 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -dev \ No newline at end of file +stable \ No newline at end of file diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 9caa7776db..bb971041a5 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -1,14 +1,9 @@ -use std::process::Command; - fn main() { println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7"); if let Ok(value) = std::env::var("ZED_MIXPANEL_TOKEN") { println!("cargo:rustc-env=ZED_MIXPANEL_TOKEN={value}"); } - if let Ok(value) = std::env::var("ZED_AMPLITUDE_API_KEY") { - println!("cargo:rustc-env=ZED_AMPLITUDE_API_KEY={value}"); - } if let Ok(value) = std::env::var("ZED_PREVIEW_CHANNEL") { println!("cargo:rustc-env=ZED_PREVIEW_CHANNEL={value}"); } @@ -21,35 +16,12 @@ fn main() { println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); } + // Weakly link ReplayKit to ensure Zed can be used on macOS 10.15+. + println!("cargo:rustc-link-arg=-Wl,-weak_framework,ReplayKit"); + // Seems to be required to enable Swift concurrency println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift"); // Register exported Objective-C selectors, protocols, etc println!("cargo:rustc-link-arg=-Wl,-ObjC"); - - let output = Command::new("npm") - .current_dir("../../styles") - .args(["install", "--no-save"]) - .output() - .expect("failed to run npm"); - if !output.status.success() { - panic!( - "failed to install theme dependencies {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - let output = Command::new("npm") - .current_dir("../../styles") - .args(["run", "build"]) - .output() - .expect("failed to run npm"); - if !output.status.success() { - panic!( - "build script failed {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - println!("cargo:rerun-if-changed=../../styles/src"); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9d0f8b33e8..74a38599ec 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -39,7 +39,10 @@ use settings::watched_json::{watch_keymap_file, watch_settings_file, WatchedJson use theme::ThemeRegistry; use util::{ResultExt, TryFutureExt}; use workspace::{self, AppState, ItemHandle, NewFile, OpenPaths, Workspace}; -use zed::{self, build_window_options, initialize_workspace, languages, menus, RELEASE_CHANNEL}; +use zed::{ + self, build_window_options, initialize_workspace, languages, menus, RELEASE_CHANNEL, + RELEASE_CHANNEL_NAME, +}; fn main() { let http = http::client(); @@ -52,9 +55,10 @@ fn main() { .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); init_panic_hook(app_version, http.clone(), app.background()); - let db = app - .background() - .spawn(async move { project::Db::open(&*zed::paths::DB_DIR) }); + + let db = app.background().spawn(async move { + project::Db::open(&*zed::paths::DB_DIR, RELEASE_CHANNEL_NAME.as_str()) + }); load_embedded_fonts(&app); @@ -86,6 +90,9 @@ fn main() { }); app.run(move |cx| { + cx.set_global(*RELEASE_CHANNEL); + cx.set_global(HomeDir(zed::paths::HOME.to_path_buf())); + let client = client::Client::new(http.clone(), cx); let mut languages = LanguageRegistry::new(login_shell_env_loaded); languages.set_language_server_download_dir(zed::paths::LANGUAGES_DIR.clone()); @@ -97,9 +104,6 @@ fn main() { let (settings_file_content, keymap_file) = cx.background().block(config_files).unwrap(); - cx.set_global(*RELEASE_CHANNEL); - cx.set_global(HomeDir(zed::paths::HOME.to_path_buf())); - //Setup settings global before binding actions cx.set_global(SettingsFile::new( &*zed::paths::SETTINGS, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6536d83ce6..06ab195f91 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -71,12 +71,12 @@ actions!( const MIN_FONT_SIZE: f32 = 6.0; lazy_static! { - static ref RELEASE_CHANNEL_NAME: String = + pub static ref RELEASE_CHANNEL_NAME: String = env::var("ZED_RELEASE_CHANNEL").unwrap_or(include_str!("../RELEASE_CHANNEL").to_string()); pub static ref RELEASE_CHANNEL: ReleaseChannel = match RELEASE_CHANNEL_NAME.as_str() { "dev" => ReleaseChannel::Dev, "preview" => ReleaseChannel::Preview, - "stable" => ReleaseChannel::Preview, + "stable" => ReleaseChannel::Stable, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }; } @@ -389,11 +389,7 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { } fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { - let app_name = match *cx.global::() { - ReleaseChannel::Dev => "Zed Dev", - ReleaseChannel::Preview => "Zed Preview", - ReleaseChannel::Stable => "Zed", - }; + let app_name = cx.global::().name(); let version = env!("CARGO_PKG_VERSION"); cx.prompt( gpui::PromptLevel::Info, diff --git a/script/amplitude_release/main.py b/script/amplitude_release/main.py deleted file mode 100644 index 160e40b66c..0000000000 --- a/script/amplitude_release/main.py +++ /dev/null @@ -1,30 +0,0 @@ -import datetime -import sys - -from amplitude_python_sdk.v2.clients.releases_client import ReleasesAPIClient -from amplitude_python_sdk.v2.models.releases import Release - - -def main(): - version = sys.argv[1] - version = version.removeprefix("v") - - api_key = sys.argv[2] - secret_key = sys.argv[3] - - current_datetime = datetime.datetime.now(datetime.timezone.utc) - current_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S") - - release = Release( - title=version, - version=version, - release_start=current_datetime, - created_by="GitHub Release Workflow", - chart_visibility=True - ) - - ReleasesAPIClient(api_key=api_key, secret_key=secret_key).create(release) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/script/amplitude_release/requirements.txt b/script/amplitude_release/requirements.txt deleted file mode 100644 index 7ed3ea6515..0000000000 --- a/script/amplitude_release/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -amplitude-python-sdk==0.2.0 \ No newline at end of file diff --git a/script/bump-app-version b/script/bump-app-version deleted file mode 100755 index a628eb8a6c..0000000000 --- a/script/bump-app-version +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -channel=$(cat crates/zed/RELEASE_CHANNEL) - -tag_suffix="" -case $channel; in - stable) - ;; - preview) - tag_suffix="-pre" - ;; - *) - echo "do this on a release branch where RELEASE_CHANNEL is either 'preview' or 'stable'" >&2 - exit 1 - ;; -esac - -exec script/lib/bump-version.sh zed v $tag_suffix $@ diff --git a/script/bump-collab-version b/script/bump-collab-version index cc8bb91dbf..ec64c42e2b 100755 --- a/script/bump-collab-version +++ b/script/bump-collab-version @@ -1,3 +1,8 @@ #!/bin/bash -exec script/lib/bump-version.sh collab collab-v '' $@ +if [[ $# < 1 ]]; then + echo "Missing version increment (major, minor, or patch)" >&2 + exit 1 +fi + +exec script/lib/bump-version.sh collab collab-v '' $1 diff --git a/script/railcar b/script/bump-zed-minor-versions similarity index 85% rename from script/railcar rename to script/bump-zed-minor-versions index 7c98dab212..d63210d4f3 100755 --- a/script/railcar +++ b/script/bump-zed-minor-versions @@ -7,11 +7,11 @@ which cargo-set-version > /dev/null || cargo install cargo-edit # Ensure we're in a clean state on an up-to-date `main` branch. if [[ -n $(git status --short --untracked-files=no) ]]; then - echo "Can't roll the railcars with uncommitted changes" + echo "can't bump versions with uncommitted changes" exit 1 fi if [[ $(git rev-parse --abbrev-ref HEAD) != "main" ]]; then - echo "Run this command on the main branch" + echo "this command must be run on main" exit 1 fi git pull -q --ff-only origin main @@ -28,7 +28,7 @@ next_minor=$(expr $minor + 1) minor_branch_name="v${major}.${minor}.x" prev_minor_branch_name="v${major}.${prev_minor}.x" next_minor_branch_name="v${major}.${next_minor}.x" -preview_tag_name="v{major}.{minor}.{patch}-pre" +preview_tag_name="v${major}.${minor}.${patch}-pre" function cleanup { git checkout -q main @@ -71,13 +71,13 @@ if git show-ref --quiet refs/tags/${stable_tag_name}; then fi old_prev_minor_sha=$(git rev-parse HEAD) echo -n stable > crates/zed/RELEASE_CHANNEL -git commit -q --all --message "Stable ${prev_minor_branch_name}" +git commit -q --all --message "${prev_minor_branch_name} stable" git tag ${stable_tag_name} echo "Creating new preview branch ${minor_branch_name}..." git checkout -q -b ${minor_branch_name} echo -n preview > crates/zed/RELEASE_CHANNEL -git commit -q --all --message "Preview ${minor_branch_name}" +git commit -q --all --message "${minor_branch_name} preview" git tag ${preview_tag_name} echo "Preparing main for version ${next_minor_branch_name}..." @@ -86,12 +86,13 @@ git clean -q -dff old_main_sha=$(git rev-parse HEAD) cargo set-version --package zed --bump minor cargo check -q -git commit -q --all --message "Dev ${next_minor_branch_name}" +git commit -q --all --message "${next_minor_branch_name} dev" cat <&2 + exit 1 + ;; +esac + +exec script/lib/bump-version.sh zed v "$tag_suffix" patch diff --git a/script/generate-doc-diagrams b/script/generate-doc-diagrams deleted file mode 100755 index 778725282b..0000000000 --- a/script/generate-doc-diagrams +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Install the `plantuml` utility if it is not already installed. -if [[ -x plantuml ]]; then - brew install plantuml -fi - -# Generate SVGs from all of the UML files. -plantuml \ - -nometadata \ - -overwrite \ - -tsvg \ - -o ../svg \ - docs/diagrams/src/* \ No newline at end of file diff --git a/script/lib/bump-version.sh b/script/lib/bump-version.sh index 4b9a1985eb..7800cc4408 100755 --- a/script/lib/bump-version.sh +++ b/script/lib/bump-version.sh @@ -2,18 +2,13 @@ set -eu -if [[ $# < 4 ]]; then - echo "Missing version increment (major, minor, or patch)" >&2 - exit 1 -fi - package=$1 tag_prefix=$2 tag_suffix=$3 version_increment=$4 if [[ -n $(git status --short --untracked-files=no) ]]; then - echo "Can't push a new version with uncommitted changes" + echo "can't bump version with uncommitted changes" exit 1 fi @@ -33,11 +28,11 @@ cat <