Compare commits

...

19 Commits

Author SHA1 Message Date
Joseph T Lyons
e6eee0a85e v0.62.x stable 2022-11-02 12:57:31 -04:00
Max Brunsfeld
d00831e704 Merge pull request #1838 from zed-industries/tab-panic
Fix panic when hitting tab at the beginning of a line with mixed tab/…
2022-11-02 12:44:27 -04:00
Joseph T. Lyons
577a3bcc33 Merge pull request #1842 from zed-industries/telemetry-corrections
Telemetry corrections
2022-11-02 12:43:01 -04:00
Joseph T. Lyons
fa511abc9b Merge pull request #1824 from zed-industries/change-telemetry-event-field-name-to-app
Change telemetry event field name to app
2022-11-02 12:39:30 -04:00
Joseph T. Lyons
bf4331e8cf Merge pull request #1829 from zed-industries/add-release-channel-information-to-telemetry-events
Add release channel information to telemetry events
2022-10-31 14:35:34 -07:00
Max Brunsfeld
8e7f711371 Merge pull request #1840 from zed-industries/build-themes-in-assets-crate
Generate themes before compiling the 'assets' crate
2022-10-31 14:19:31 -07:00
Max Brunsfeld
df837283e8 Merge pull request #1839 from zed-industries/contact-list-project-color
Don't use 'on' background color for projects in the contact list
2022-10-31 12:39:58 -07:00
Mikayla Maki
4e788b1818 zed 0.62.3 2022-10-31 11:15:29 -07:00
Mikayla Maki
b8733feeff Merge pull request #1833 from zed-industries/add-channel-to-db
Added channel info to database directories
2022-10-31 11:10:40 -07:00
Mikayla Maki
e8b917e3f0 Merge pull request #1832 from zed-industries/upgrade-migration-lib
Upgraded migration library to remove panic
2022-10-31 11:10:23 -07:00
Max Brunsfeld
5c0083d4dd zed 0.62.2 2022-10-27 16:49:05 -07:00
Max Brunsfeld
85cb68c3f5 Merge pull request #1830 from zed-industries/auto-update-filename
Make auto-update handle an app bundle name other than 'Zed.app'
2022-10-27 16:48:42 -07:00
Max Brunsfeld
7aec6b5531 zed 0.62.1 2022-10-27 15:45:30 -07:00
Max Brunsfeld
4dab0f89f4 Tweak version-bumping scripts 2022-10-27 15:45:07 -07:00
Max Brunsfeld
d2f6b315a3 Avoid posting in Discord about preview releases (for now)
Co-authored-by: Joseph Lyons <joseph@zed.dev>
2022-10-27 15:44:32 -07:00
Kay Simmons
62d4473c3f Merge pull request #1827 from zed-industries/fix-keymap-resolution-fallback
Keymap resolution fallbacks
2022-10-27 15:44:05 -07:00
Max Brunsfeld
6e2d3aae68 Merge pull request #1828 from zed-industries/following-scrollbar
Show scrollbar when scrolling while following
2022-10-27 15:43:53 -07:00
Max Brunsfeld
e5959483ed Merge pull request #1825 from zed-industries/update-notification-release-channel
Indicate release channel in auto-update notification
2022-10-27 11:01:41 -07:00
Max Brunsfeld
e13012c48e Preview 0.62.x 2022-10-26 21:09:00 -07:00
33 changed files with 341 additions and 618 deletions

View File

@@ -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

View File

@@ -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: |
@@ -19,15 +20,3 @@ jobs:
### Changelog
${{ github.event.release.body }}
```
amplitude_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
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 }}

5
Cargo.lock generated
View File

@@ -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.3"
dependencies = [
"activity_indicator",
"anyhow",

29
crates/assets/build.rs Normal file
View File

@@ -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");
}

View File

@@ -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?;

View File

@@ -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::<Settings>().theme.clone();
let theme = &theme.update_notification;
let app_name = cx.global::<ReleaseChannel>().name();
MouseEventHandler::<ViewReleaseNotes>::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()

View File

@@ -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<dyn HttpClient>,
executor: Arc<Background>,
session_id: u128,
state: Mutex<AmplitudeTelemetryState>,
}
#[derive(Default)]
struct AmplitudeTelemetryState {
metrics_id: Option<Arc<str>>,
device_id: Option<Arc<str>>,
app_version: Option<Arc<str>>,
os_version: Option<Arc<str>>,
os_name: &'static str,
queue: Vec<AmplitudeEvent>,
next_event_id: usize,
flush_task: Option<Task<()>>,
log_file: Option<NamedTempFile>,
}
const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch";
lazy_static! {
static ref AMPLITUDE_API_KEY: Option<String> = 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<AmplitudeEvent>,
}
#[derive(Serialize)]
struct AmplitudeEvent {
#[serde(skip_serializing_if = "Option::is_none")]
user_id: Option<Arc<str>>,
device_id: Option<Arc<str>>,
event_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
event_properties: Option<Map<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
user_properties: Option<Map<String, Value>>,
os_name: &'static str,
os_version: Option<Arc<str>>,
app_version: Option<Arc<str>>,
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<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
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<PathBuf> {
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
}
pub fn start(self: &Arc<Self>, 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<Self>,
metrics_id: Option<String>,
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<Self>, kind: &str, properties: Value) {
self.report_event_with_user_properties(kind, properties, Default::default());
}
fn report_event_with_user_properties(
self: &Arc<Self>,
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<Self>) {
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();
}
}
}

View File

@@ -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<Peer>,
http: Arc<dyn HttpClient>,
telemetry: Arc<Telemetry>,
amplitude_telemetry: Arc<AmplitudeTelemetry>,
state: RwLock<ClientState>,
#[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<PathBuf> {
self.amplitude_telemetry.log_file_path();
self.telemetry.log_file_path()
}
}

View File

@@ -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,
@@ -31,7 +32,9 @@ pub struct Telemetry {
struct TelemetryState {
metrics_id: Option<Arc<str>>,
device_id: Option<Arc<str>>,
app: &'static str,
app_version: Option<Arc<str>>,
release_channel: Option<&'static str>,
os_version: Option<Arc<str>>,
os_name: &'static str,
queue: Vec<MixpanelEvent>,
@@ -67,11 +70,18 @@ struct MixpanelEventProperties {
// Custom fields
#[serde(skip_serializing_if = "Option::is_none", flatten)]
event_properties: Option<Map<String, Value>>,
#[serde(rename = "OS Name")]
os_name: &'static str,
#[serde(rename = "OS Version")]
os_version: Option<Arc<str>>,
#[serde(rename = "Release Channel")]
release_channel: Option<&'static str>,
#[serde(rename = "App Version")]
app_version: Option<Arc<str>>,
#[serde(rename = "Signed In")]
signed_in: bool,
platform: &'static str,
#[serde(rename = "App")]
app: &'static str,
}
#[derive(Serialize)]
@@ -99,19 +109,20 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
impl Telemetry {
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
let platform = cx.platform();
let release_channel = if cx.has_global::<ReleaseChannel>() {
Some(cx.global::<ReleaseChannel>().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: "Zed",
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 +205,7 @@ 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 }),
}])?;
let request = Request::post(MIXPANEL_ENGAGE_URL)
.header("Content-Type", "application/json")
@@ -227,9 +238,10 @@ 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",
app: state.app,
},
};
state.queue.push(event);

View File

@@ -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();
}
}

View File

@@ -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"

View File

@@ -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(&current_db_dir)
.expect("Should be able to create the database directory");
let db_path = current_db_dir.join(Path::new("db.sqlite"));

View File

@@ -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;

View File

@@ -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())

View File

@@ -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, _| {
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);

View File

@@ -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 {

View File

@@ -13,16 +13,11 @@ extern "C" {
}
pub struct Matcher {
pending: HashMap<usize, Pending>,
pending_views: HashMap<usize, Context>,
pending_keystrokes: Vec<Keystroke>,
keymap: Keymap,
}
#[derive(Default)]
struct Pending {
keystrokes: Vec<Keystroke>,
context: Option<Context>,
}
#[derive(Default)]
pub struct Keymap {
bindings: Vec<Binding>,
@@ -77,21 +72,21 @@ where
pub enum MatchResult {
None,
Pending,
Match {
view_id: usize,
action: Box<dyn Action>,
},
Matches(Vec<(usize, Box<dyn Action>)>),
}
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<T: IntoIterator<Item = Binding>>(&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<A: Action>(action: &Option<Box<dyn Action>>) -> 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<Box<dyn Action>> {
if let MatchResult::Match { action, .. } =
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), dispatch_path)
{
Some(action.boxed_clone())
} else {
None
}
}
}
}

View File

@@ -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<Point>, 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::<String>(),
))
} else {
match new_size.len.cmp(&current_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::<String>(),
))
}
match new_size.len.cmp(&current_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::<String>(),
))
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,
}
}

View File

@@ -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![]

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.62.0"
version = "0.62.3"
[lib]
name = "zed"

View File

@@ -1 +1 @@
dev
stable

View File

@@ -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}");
}
@@ -26,30 +21,4 @@ fn main() {
// 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");
}

View File

@@ -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,

View File

@@ -71,7 +71,7 @@ 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,
@@ -389,11 +389,7 @@ fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
}
fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
let app_name = match *cx.global::<ReleaseChannel>() {
ReleaseChannel::Dev => "Zed Dev",
ReleaseChannel::Preview => "Zed Preview",
ReleaseChannel::Stable => "Zed",
};
let app_name = cx.global::<ReleaseChannel>().name();
let version = env!("CARGO_PKG_VERSION");
cx.prompt(
gpui::PromptLevel::Info,

View File

@@ -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()

View File

@@ -1 +0,0 @@
amplitude-python-sdk==0.2.0

View File

@@ -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 $@

View File

@@ -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

View File

@@ -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 <<MESSAGE
Locally rolled the railcars.
Prepared new Zed versions locally.
To push this:
git push origin \\
${preview_tag_name} \\
${stable_tag_name} \\
@@ -100,10 +101,11 @@ To push this:
main
To undo this:
git push -f . \\
git reset --hard ${old_main_sha} && git push -f . \\
:${preview_tag_name} \\
:${stable_tag_name} \\
:${minor_branch_name} \\
${old_prev_minor_sha}:${prev_minor_branch_name} \\
${old_main_sha}:main
${old_prev_minor_sha}:${prev_minor_branch_name}
MESSAGE

18
script/bump-zed-patch-version Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
channel=$(cat crates/zed/RELEASE_CHANNEL)
tag_suffix=""
case $channel in
stable)
;;
preview)
tag_suffix="-pre"
;;
*)
echo "this must be run on a stable or preview release branch" >&2
exit 1
;;
esac
exec script/lib/bump-version.sh zed v $tag_suffix patch

View File

@@ -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/*

View File

@@ -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 <<MESSAGE
Locally committed and tagged ${package} version ${new_version}
To push this:
git push origin \
${tag_name} \
${branch_name}
git push origin ${tag_name} ${branch_name}
To undo this:
git tag -d ${tag_name} && \
git reset --hard ${old_sha}
git reset --hard ${old_sha} && git tag -d ${tag_name}
MESSAGE

View File

@@ -63,7 +63,7 @@ export default function contactsPanel(colorScheme: ColorScheme) {
top: 4,
},
margin: {
left: 6
left: 6,
},
},
userQueryEditorHeight: 33,
@@ -151,7 +151,7 @@ export default function contactsPanel(colorScheme: ColorScheme) {
color: foreground(layer, "on"),
},
callingIndicator: {
...text(layer, "mono", "variant", { size: "xs" })
...text(layer, "mono", "variant", { size: "xs" }),
},
treeBranch: {
color: borderColor(layer),
@@ -165,7 +165,7 @@ export default function contactsPanel(colorScheme: ColorScheme) {
},
projectRow: {
...projectRow,
background: background(layer, "on"),
background: background(layer),
icon: {
margin: { left: nameMargin },
color: foreground(layer, "variant"),
@@ -176,11 +176,11 @@ export default function contactsPanel(colorScheme: ColorScheme) {
...text(layer, "mono", { size: "sm" }),
},
hover: {
background: background(layer, "on", "hovered"),
background: background(layer, "hovered"),
},
active: {
background: background(layer, "on", "active"),
background: background(layer, "active"),
},
},
}
};
}