Compare commits
19 Commits
fix-git-ht
...
v0.62.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6eee0a85e | ||
|
|
d00831e704 | ||
|
|
577a3bcc33 | ||
|
|
fa511abc9b | ||
|
|
bf4331e8cf | ||
|
|
8e7f711371 | ||
|
|
df837283e8 | ||
|
|
4e788b1818 | ||
|
|
b8733feeff | ||
|
|
e8b917e3f0 | ||
|
|
5c0083d4dd | ||
|
|
85cb68c3f5 | ||
|
|
7aec6b5531 | ||
|
|
4dab0f89f4 | ||
|
|
d2f6b315a3 | ||
|
|
62d4473c3f | ||
|
|
6e2d3aae68 | ||
|
|
e5959483ed | ||
|
|
e13012c48e |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
13
.github/workflows/release_actions.yml
vendored
13
.github/workflows/release_actions.yml
vendored
@@ -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
5
Cargo.lock
generated
@@ -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
29
crates/assets/build.rs
Normal 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");
|
||||
}
|
||||
@@ -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?;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(¤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::<String>(),
|
||||
))
|
||||
}
|
||||
|
||||
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::<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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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![]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
@@ -1 +0,0 @@
|
||||
amplitude-python-sdk==0.2.0
|
||||
@@ -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 $@
|
||||
@@ -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
|
||||
|
||||
@@ -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
18
script/bump-zed-patch-version
Executable 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
|
||||
@@ -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/*
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user