Merge remote-tracking branch 'origin/main' into assistant-2
This commit is contained in:
commit
7a78e64831
68 changed files with 781 additions and 597 deletions
|
@ -159,10 +159,7 @@ impl Bundle {
|
|||
fn path(&self) -> &Path {
|
||||
match self {
|
||||
Self::App { app_bundle, .. } => app_bundle,
|
||||
Self::LocalPath {
|
||||
executable: excutable,
|
||||
..
|
||||
} => excutable,
|
||||
Self::LocalPath { executable, .. } => executable,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -776,15 +776,6 @@ impl Client {
|
|||
if credentials.is_none() && try_keychain {
|
||||
credentials = read_credentials_from_keychain(cx);
|
||||
read_from_keychain = credentials.is_some();
|
||||
if read_from_keychain {
|
||||
cx.read(|cx| {
|
||||
self.telemetry().report_mixpanel_event(
|
||||
"read credentials from keychain",
|
||||
Default::default(),
|
||||
*settings::get::<TelemetrySettings>(cx),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if credentials.is_none() {
|
||||
let mut status_rx = self.status();
|
||||
|
@ -1072,11 +1063,8 @@ impl Client {
|
|||
) -> Task<Result<Credentials>> {
|
||||
let platform = cx.platform();
|
||||
let executor = cx.background();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let http = self.http.clone();
|
||||
|
||||
let telemetry_settings = cx.read(|cx| *settings::get::<TelemetrySettings>(cx));
|
||||
|
||||
executor.clone().spawn(async move {
|
||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||
|
@ -1159,12 +1147,6 @@ impl Client {
|
|||
.context("failed to decrypt access token")?;
|
||||
platform.activate(true);
|
||||
|
||||
telemetry.report_mixpanel_event(
|
||||
"authenticate with browser",
|
||||
Default::default(),
|
||||
telemetry_settings,
|
||||
);
|
||||
|
||||
Ok(Credentials {
|
||||
user_id: user_id.parse()?,
|
||||
access_token,
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
executor::Background,
|
||||
serde_json::{self, value::Map, Value},
|
||||
AppContext, Task,
|
||||
};
|
||||
use gpui::{executor::Background, serde_json, AppContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
env,
|
||||
io::Write,
|
||||
|
@ -19,7 +14,7 @@ use std::{
|
|||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use util::http::HttpClient;
|
||||
use util::{channel::ReleaseChannel, post_inc, ResultExt, TryFutureExt};
|
||||
use util::{channel::ReleaseChannel, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct Telemetry {
|
||||
|
@ -37,23 +32,15 @@ struct TelemetryState {
|
|||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
architecture: &'static str,
|
||||
mixpanel_events_queue: Vec<MixpanelEvent>,
|
||||
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
|
||||
next_mixpanel_event_id: usize,
|
||||
flush_mixpanel_events_task: Option<Task<()>>,
|
||||
flush_clickhouse_events_task: Option<Task<()>>,
|
||||
log_file: Option<NamedTempFile>,
|
||||
is_staff: Option<bool>,
|
||||
}
|
||||
|
||||
const MIXPANEL_EVENTS_URL: &'static str = "https://api.mixpanel.com/track";
|
||||
const MIXPANEL_ENGAGE_URL: &'static str = "https://api.mixpanel.com/engage#profile-set";
|
||||
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
|
||||
|
||||
lazy_static! {
|
||||
static ref MIXPANEL_TOKEN: Option<String> = std::env::var("ZED_MIXPANEL_TOKEN")
|
||||
.ok()
|
||||
.or_else(|| option_env!("ZED_MIXPANEL_TOKEN").map(|key| key.to_string()));
|
||||
static ref CLICKHOUSE_EVENTS_URL: String =
|
||||
format!("{}{}", *ZED_SERVER_URL, CLICKHOUSE_EVENTS_URL_PATH);
|
||||
}
|
||||
|
@ -95,47 +82,6 @@ pub enum ClickhouseEvent {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct MixpanelEvent {
|
||||
event: String,
|
||||
properties: MixpanelEventProperties,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct MixpanelEventProperties {
|
||||
// Mixpanel required fields
|
||||
#[serde(skip_serializing_if = "str::is_empty")]
|
||||
token: &'static str,
|
||||
time: u128,
|
||||
#[serde(rename = "distinct_id")]
|
||||
installation_id: Option<Arc<str>>,
|
||||
#[serde(rename = "$insert_id")]
|
||||
insert_id: usize,
|
||||
// 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,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MixpanelEngageRequest {
|
||||
#[serde(rename = "$token")]
|
||||
token: &'static str,
|
||||
#[serde(rename = "$distinct_id")]
|
||||
installation_id: Arc<str>,
|
||||
#[serde(rename = "$set")]
|
||||
set: Value,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
const MAX_QUEUE_LEN: usize = 1;
|
||||
|
||||
|
@ -168,29 +114,13 @@ impl Telemetry {
|
|||
release_channel,
|
||||
installation_id: None,
|
||||
metrics_id: None,
|
||||
mixpanel_events_queue: Default::default(),
|
||||
clickhouse_events_queue: Default::default(),
|
||||
flush_mixpanel_events_task: Default::default(),
|
||||
flush_clickhouse_events_task: Default::default(),
|
||||
next_mixpanel_event_id: 0,
|
||||
log_file: None,
|
||||
is_staff: None,
|
||||
}),
|
||||
});
|
||||
|
||||
if MIXPANEL_TOKEN.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
|
||||
}
|
||||
|
||||
|
@ -218,20 +148,9 @@ impl Telemetry {
|
|||
let mut state = this.state.lock();
|
||||
state.installation_id = Some(installation_id.clone());
|
||||
|
||||
for event in &mut state.mixpanel_events_queue {
|
||||
event
|
||||
.properties
|
||||
.installation_id
|
||||
.get_or_insert_with(|| installation_id.clone());
|
||||
}
|
||||
|
||||
let has_mixpanel_events = !state.mixpanel_events_queue.is_empty();
|
||||
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
|
||||
drop(state);
|
||||
|
||||
if has_mixpanel_events {
|
||||
this.flush_mixpanel_events();
|
||||
}
|
||||
drop(state);
|
||||
|
||||
if has_clickhouse_events {
|
||||
this.flush_clickhouse_events();
|
||||
|
@ -256,37 +175,11 @@ impl Telemetry {
|
|||
return;
|
||||
}
|
||||
|
||||
let this = self.clone();
|
||||
let mut state = self.state.lock();
|
||||
let installation_id = state.installation_id.clone();
|
||||
let metrics_id: Option<Arc<str>> = metrics_id.map(|id| id.into());
|
||||
state.metrics_id = metrics_id.clone();
|
||||
state.is_staff = Some(is_staff);
|
||||
drop(state);
|
||||
|
||||
if let Some((token, installation_id)) = MIXPANEL_TOKEN.as_ref().zip(installation_id) {
|
||||
self.executor
|
||||
.spawn(
|
||||
async move {
|
||||
let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest {
|
||||
token,
|
||||
installation_id,
|
||||
set: json!({
|
||||
"Staff": is_staff,
|
||||
"ID": metrics_id,
|
||||
"App": true
|
||||
}),
|
||||
}])?;
|
||||
|
||||
this.http_client
|
||||
.post_json(MIXPANEL_ENGAGE_URL, json_bytes.into())
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_clickhouse_event(
|
||||
|
@ -310,7 +203,7 @@ impl Telemetry {
|
|||
});
|
||||
|
||||
if state.installation_id.is_some() {
|
||||
if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN {
|
||||
if state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
||||
drop(state);
|
||||
self.flush_clickhouse_events();
|
||||
} else {
|
||||
|
@ -324,55 +217,6 @@ impl Telemetry {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn report_mixpanel_event(
|
||||
self: &Arc<Self>,
|
||||
kind: &str,
|
||||
properties: Value,
|
||||
telemetry_settings: TelemetrySettings,
|
||||
) {
|
||||
if !telemetry_settings.metrics {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut state = self.state.lock();
|
||||
let event = MixpanelEvent {
|
||||
event: kind.into(),
|
||||
properties: MixpanelEventProperties {
|
||||
token: "",
|
||||
time: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
installation_id: state.installation_id.clone(),
|
||||
insert_id: post_inc(&mut state.next_mixpanel_event_id),
|
||||
event_properties: if let Value::Object(properties) = properties {
|
||||
Some(properties)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
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(),
|
||||
},
|
||||
};
|
||||
state.mixpanel_events_queue.push(event);
|
||||
if state.installation_id.is_some() {
|
||||
if state.mixpanel_events_queue.len() >= MAX_QUEUE_LEN {
|
||||
drop(state);
|
||||
self.flush_mixpanel_events();
|
||||
} else {
|
||||
let this = self.clone();
|
||||
let executor = self.executor.clone();
|
||||
state.flush_mixpanel_events_task = Some(self.executor.spawn(async move {
|
||||
executor.timer(DEBOUNCE_INTERVAL).await;
|
||||
this.flush_mixpanel_events();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metrics_id(self: &Arc<Self>) -> Option<Arc<str>> {
|
||||
self.state.lock().metrics_id.clone()
|
||||
}
|
||||
|
@ -385,44 +229,6 @@ impl Telemetry {
|
|||
self.state.lock().is_staff
|
||||
}
|
||||
|
||||
fn flush_mixpanel_events(self: &Arc<Self>) {
|
||||
let mut state = self.state.lock();
|
||||
let mut events = mem::take(&mut state.mixpanel_events_queue);
|
||||
state.flush_mixpanel_events_task.take();
|
||||
drop(state);
|
||||
|
||||
if let Some(token) = MIXPANEL_TOKEN.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 &mut events {
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, event)?;
|
||||
file.write_all(&json_bytes)?;
|
||||
file.write(b"\n")?;
|
||||
|
||||
event.properties.token = token;
|
||||
}
|
||||
}
|
||||
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, &events)?;
|
||||
this.http_client
|
||||
.post_json(MIXPANEL_EVENTS_URL, json_bytes.into())
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_clickhouse_events(self: &Arc<Self>) {
|
||||
let mut state = self.state.lock();
|
||||
let mut events = mem::take(&mut state.clickhouse_events_queue);
|
||||
|
|
|
@ -1424,7 +1424,7 @@ async fn join_project(
|
|||
)?;
|
||||
}
|
||||
|
||||
for settings_file in dbg!(worktree.settings_files) {
|
||||
for settings_file in worktree.settings_files {
|
||||
session.peer.send(
|
||||
session.connection_id,
|
||||
proto::UpdateWorktreeSettings {
|
||||
|
@ -1554,8 +1554,6 @@ async fn update_worktree_settings(
|
|||
message: proto::UpdateWorktreeSettings,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
dbg!(&message);
|
||||
|
||||
let guest_connection_ids = session
|
||||
.db()
|
||||
.await
|
||||
|
|
|
@ -472,7 +472,7 @@ impl CollabTitlebarItem {
|
|||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistant width for both text variations
|
||||
//TODO: Ensure this button has consistent width for both text variations
|
||||
let style = titlebar.share_button.style_for(state, false);
|
||||
Label::new(label, style.text.clone())
|
||||
.contained()
|
||||
|
|
|
@ -4,7 +4,7 @@ mod sign_in;
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
|
||||
|
@ -127,7 +127,7 @@ impl CopilotServer {
|
|||
struct RunningCopilotServer {
|
||||
lsp: Arc<LanguageServer>,
|
||||
sign_in_status: SignInStatus,
|
||||
registered_buffers: HashMap<u64, RegisteredBuffer>,
|
||||
registered_buffers: HashMap<usize, RegisteredBuffer>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -163,7 +163,6 @@ impl Status {
|
|||
}
|
||||
|
||||
struct RegisteredBuffer {
|
||||
id: u64,
|
||||
uri: lsp::Url,
|
||||
language_id: String,
|
||||
snapshot: BufferSnapshot,
|
||||
|
@ -178,13 +177,13 @@ impl RegisteredBuffer {
|
|||
buffer: &ModelHandle<Buffer>,
|
||||
cx: &mut ModelContext<Copilot>,
|
||||
) -> oneshot::Receiver<(i32, BufferSnapshot)> {
|
||||
let id = self.id;
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
|
||||
if buffer.read(cx).version() == self.snapshot.version {
|
||||
let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
|
||||
} else {
|
||||
let buffer = buffer.downgrade();
|
||||
let id = buffer.id();
|
||||
let prev_pending_change =
|
||||
mem::replace(&mut self.pending_buffer_change, Task::ready(None));
|
||||
self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
|
||||
|
@ -268,7 +267,7 @@ pub struct Copilot {
|
|||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: Arc<NodeRuntime>,
|
||||
server: CopilotServer,
|
||||
buffers: HashMap<u64, WeakModelHandle<Buffer>>,
|
||||
buffers: HashSet<WeakModelHandle<Buffer>>,
|
||||
}
|
||||
|
||||
impl Entity for Copilot {
|
||||
|
@ -375,7 +374,7 @@ impl Copilot {
|
|||
server
|
||||
.on_notification::<LogMessage, _>(|params, _cx| {
|
||||
match params.level {
|
||||
// Copilot is pretty agressive about logging
|
||||
// Copilot is pretty aggressive about logging
|
||||
0 => debug!("copilot: {}", params.message),
|
||||
1 => debug!("copilot: {}", params.message),
|
||||
_ => error!("copilot: {}", params.message),
|
||||
|
@ -559,8 +558,8 @@ impl Copilot {
|
|||
}
|
||||
|
||||
pub fn register_buffer(&mut self, buffer: &ModelHandle<Buffer>, cx: &mut ModelContext<Self>) {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
self.buffers.insert(buffer_id, buffer.downgrade());
|
||||
let weak_buffer = buffer.downgrade();
|
||||
self.buffers.insert(weak_buffer.clone());
|
||||
|
||||
if let CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: server,
|
||||
|
@ -573,8 +572,7 @@ impl Copilot {
|
|||
return;
|
||||
}
|
||||
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
registered_buffers.entry(buffer_id).or_insert_with(|| {
|
||||
registered_buffers.entry(buffer.id()).or_insert_with(|| {
|
||||
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||
let language_id = id_for_language(buffer.read(cx).language());
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
@ -592,7 +590,6 @@ impl Copilot {
|
|||
.log_err();
|
||||
|
||||
RegisteredBuffer {
|
||||
id: buffer_id,
|
||||
uri,
|
||||
language_id,
|
||||
snapshot,
|
||||
|
@ -603,8 +600,8 @@ impl Copilot {
|
|||
this.handle_buffer_event(buffer, event, cx).log_err();
|
||||
}),
|
||||
cx.observe_release(buffer, move |this, _buffer, _cx| {
|
||||
this.buffers.remove(&buffer_id);
|
||||
this.unregister_buffer(buffer_id);
|
||||
this.buffers.remove(&weak_buffer);
|
||||
this.unregister_buffer(&weak_buffer);
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
@ -619,8 +616,7 @@ impl Copilot {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Ok(server) = self.server.as_running() {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer_id) {
|
||||
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
|
||||
match event {
|
||||
language::Event::Edited => {
|
||||
let _ = registered_buffer.report_changes(&buffer, cx);
|
||||
|
@ -674,9 +670,9 @@ impl Copilot {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_buffer(&mut self, buffer_id: u64) {
|
||||
fn unregister_buffer(&mut self, buffer: &WeakModelHandle<Buffer>) {
|
||||
if let Ok(server) = self.server.as_running() {
|
||||
if let Some(buffer) = server.registered_buffers.remove(&buffer_id) {
|
||||
if let Some(buffer) = server.registered_buffers.remove(&buffer.id()) {
|
||||
server
|
||||
.lsp
|
||||
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||
|
@ -779,8 +775,7 @@ impl Copilot {
|
|||
Err(error) => return Task::ready(Err(error)),
|
||||
};
|
||||
let lsp = server.lsp.clone();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let registered_buffer = server.registered_buffers.get_mut(&buffer_id).unwrap();
|
||||
let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
|
||||
let snapshot = registered_buffer.report_changes(buffer, cx);
|
||||
let buffer = buffer.read(cx);
|
||||
let uri = registered_buffer.uri.clone();
|
||||
|
@ -850,7 +845,7 @@ impl Copilot {
|
|||
lsp_status: request::SignInStatus,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.buffers.retain(|_, buffer| buffer.is_upgradable(cx));
|
||||
self.buffers.retain(|buffer| buffer.is_upgradable(cx));
|
||||
|
||||
if let Ok(server) = self.server.as_running() {
|
||||
match lsp_status {
|
||||
|
@ -858,7 +853,7 @@ impl Copilot {
|
|||
| request::SignInStatus::MaybeOk { .. }
|
||||
| request::SignInStatus::AlreadySignedIn { .. } => {
|
||||
server.sign_in_status = SignInStatus::Authorized;
|
||||
for buffer in self.buffers.values().cloned().collect::<Vec<_>>() {
|
||||
for buffer in self.buffers.iter().cloned().collect::<Vec<_>>() {
|
||||
if let Some(buffer) = buffer.upgrade(cx) {
|
||||
self.register_buffer(&buffer, cx);
|
||||
}
|
||||
|
@ -866,14 +861,14 @@ impl Copilot {
|
|||
}
|
||||
request::SignInStatus::NotAuthorized { .. } => {
|
||||
server.sign_in_status = SignInStatus::Unauthorized;
|
||||
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||
self.unregister_buffer(buffer_id);
|
||||
for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
|
||||
self.unregister_buffer(&buffer);
|
||||
}
|
||||
}
|
||||
request::SignInStatus::NotSignedIn => {
|
||||
server.sign_in_status = SignInStatus::SignedOut;
|
||||
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||
self.unregister_buffer(buffer_id);
|
||||
for buffer in self.buffers.iter().copied().collect::<Vec<_>>() {
|
||||
self.unregister_buffer(&buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -896,9 +891,7 @@ fn uri_for_buffer(buffer: &ModelHandle<Buffer>, cx: &AppContext) -> lsp::Url {
|
|||
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||
} else {
|
||||
format!("buffer://{}", buffer.read(cx).remote_id())
|
||||
.parse()
|
||||
.unwrap()
|
||||
format!("buffer://{}", buffer.id()).parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -475,7 +475,7 @@ impl DisplaySnapshot {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator of the start positions of the occurances of `target` in the `self` after `from`
|
||||
/// Returns an iterator of the start positions of the occurrences of `target` in the `self` after `from`
|
||||
/// Stops if `condition` returns false for any of the character position pairs observed.
|
||||
pub fn find_while<'a>(
|
||||
&'a self,
|
||||
|
@ -486,7 +486,7 @@ impl DisplaySnapshot {
|
|||
Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
|
||||
}
|
||||
|
||||
/// Returns an iterator of the end positions of the occurances of `target` in the `self` before `from`
|
||||
/// Returns an iterator of the end positions of the occurrences of `target` in the `self` before `from`
|
||||
/// Stops if `condition` returns false for any of the character position pairs observed.
|
||||
pub fn reverse_find_while<'a>(
|
||||
&'a self,
|
||||
|
|
|
@ -37,6 +37,7 @@ pub use element::{
|
|||
};
|
||||
use futures::FutureExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::LayoutContext;
|
||||
use gpui::{
|
||||
actions,
|
||||
color::Color,
|
||||
|
@ -47,9 +48,8 @@ use gpui::{
|
|||
impl_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
serde_json::{self, json},
|
||||
AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, Entity,
|
||||
LayoutContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element,
|
||||
Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
|
@ -113,6 +113,12 @@ pub struct SelectNext {
|
|||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq, Default)]
|
||||
pub struct SelectPrevious {
|
||||
#[serde(default)]
|
||||
pub replace_newest: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct SelectToBeginningOfLine {
|
||||
#[serde(default)]
|
||||
|
@ -274,6 +280,7 @@ impl_actions!(
|
|||
editor,
|
||||
[
|
||||
SelectNext,
|
||||
SelectPrevious,
|
||||
SelectToBeginningOfLine,
|
||||
SelectToEndOfLine,
|
||||
ToggleCodeActions,
|
||||
|
@ -369,6 +376,7 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.add_action(Editor::add_selection_above);
|
||||
cx.add_action(Editor::add_selection_below);
|
||||
cx.add_action(Editor::select_next);
|
||||
cx.add_action(Editor::select_previous);
|
||||
cx.add_action(Editor::toggle_comments);
|
||||
cx.add_action(Editor::select_larger_syntax_node);
|
||||
cx.add_action(Editor::select_smaller_syntax_node);
|
||||
|
@ -486,6 +494,7 @@ pub struct Editor {
|
|||
columnar_selection_tail: Option<Anchor>,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
select_next_state: Option<SelectNextState>,
|
||||
select_prev_state: Option<SelectNextState>,
|
||||
selection_history: SelectionHistory,
|
||||
autoclose_regions: Vec<AutocloseRegion>,
|
||||
snippet_stack: InvalidationStack<SnippetState>,
|
||||
|
@ -544,6 +553,7 @@ pub struct EditorSnapshot {
|
|||
struct SelectionHistoryEntry {
|
||||
selections: Arc<[Selection<Anchor>]>,
|
||||
select_next_state: Option<SelectNextState>,
|
||||
select_prev_state: Option<SelectNextState>,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
}
|
||||
|
||||
|
@ -1291,6 +1301,7 @@ impl Editor {
|
|||
columnar_selection_tail: None,
|
||||
add_selections_state: None,
|
||||
select_next_state: None,
|
||||
select_prev_state: None,
|
||||
selection_history: Default::default(),
|
||||
autoclose_regions: Default::default(),
|
||||
snippet_stack: Default::default(),
|
||||
|
@ -1515,6 +1526,7 @@ impl Editor {
|
|||
let buffer = &display_map.buffer_snapshot;
|
||||
self.add_selections_state = None;
|
||||
self.select_next_state = None;
|
||||
self.select_prev_state = None;
|
||||
self.select_larger_syntax_node_stack.clear();
|
||||
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
|
||||
self.snippet_stack
|
||||
|
@ -2535,7 +2547,7 @@ impl Editor {
|
|||
.read(cx)
|
||||
.text_anchor_for_position(position.clone(), cx)?;
|
||||
|
||||
// OnTypeFormatting retuns a list of edits, no need to pass them between Zed instances,
|
||||
// OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
|
||||
// hence we do LSP request & edit on host side only — add formats to host's history.
|
||||
let push_to_lsp_host_history = true;
|
||||
// If this is not the host, append its history with new edits.
|
||||
|
@ -5223,6 +5235,101 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn select_previous(&mut self, action: &SelectPrevious, cx: &mut ViewContext<Self>) {
|
||||
self.push_to_selection_history();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let mut selections = self.selections.all::<usize>(cx);
|
||||
if let Some(mut select_prev_state) = self.select_prev_state.take() {
|
||||
let query = &select_prev_state.query;
|
||||
if !select_prev_state.done {
|
||||
let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
|
||||
let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
|
||||
let mut next_selected_range = None;
|
||||
// When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
|
||||
let bytes_before_last_selection =
|
||||
buffer.reversed_bytes_in_range(0..last_selection.start);
|
||||
let bytes_after_first_selection =
|
||||
buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
|
||||
let query_matches = query
|
||||
.stream_find_iter(bytes_before_last_selection)
|
||||
.map(|result| (last_selection.start, result))
|
||||
.chain(
|
||||
query
|
||||
.stream_find_iter(bytes_after_first_selection)
|
||||
.map(|result| (buffer.len(), result)),
|
||||
);
|
||||
for (end_offset, query_match) in query_matches {
|
||||
let query_match = query_match.unwrap(); // can only fail due to I/O
|
||||
let offset_range =
|
||||
end_offset - query_match.end()..end_offset - query_match.start();
|
||||
let display_range = offset_range.start.to_display_point(&display_map)
|
||||
..offset_range.end.to_display_point(&display_map);
|
||||
|
||||
if !select_prev_state.wordwise
|
||||
|| (!movement::is_inside_word(&display_map, display_range.start)
|
||||
&& !movement::is_inside_word(&display_map, display_range.end))
|
||||
{
|
||||
next_selected_range = Some(offset_range);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(next_selected_range) = next_selected_range {
|
||||
self.unfold_ranges([next_selected_range.clone()], false, true, cx);
|
||||
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
||||
if action.replace_newest {
|
||||
s.delete(s.newest_anchor().id);
|
||||
}
|
||||
s.insert_range(next_selected_range);
|
||||
});
|
||||
} else {
|
||||
select_prev_state.done = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.select_prev_state = Some(select_prev_state);
|
||||
} else if selections.len() == 1 {
|
||||
let selection = selections.last_mut().unwrap();
|
||||
if selection.start == selection.end {
|
||||
let word_range = movement::surrounding_word(
|
||||
&display_map,
|
||||
selection.start.to_display_point(&display_map),
|
||||
);
|
||||
selection.start = word_range.start.to_offset(&display_map, Bias::Left);
|
||||
selection.end = word_range.end.to_offset(&display_map, Bias::Left);
|
||||
selection.goal = SelectionGoal::None;
|
||||
selection.reversed = false;
|
||||
|
||||
let query = buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.collect::<String>();
|
||||
let query = query.chars().rev().collect::<String>();
|
||||
let select_state = SelectNextState {
|
||||
query: AhoCorasick::new_auto_configured(&[query]),
|
||||
wordwise: true,
|
||||
done: false,
|
||||
};
|
||||
self.unfold_ranges([selection.start..selection.end], false, true, cx);
|
||||
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
self.select_prev_state = Some(select_state);
|
||||
} else {
|
||||
let query = buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.collect::<String>();
|
||||
let query = query.chars().rev().collect::<String>();
|
||||
self.select_prev_state = Some(SelectNextState {
|
||||
query: AhoCorasick::new_auto_configured(&[query]),
|
||||
wordwise: false,
|
||||
done: false,
|
||||
});
|
||||
self.select_previous(action, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_comments(&mut self, action: &ToggleComments, cx: &mut ViewContext<Self>) {
|
||||
self.transact(cx, |this, cx| {
|
||||
let mut selections = this.selections.all::<Point>(cx);
|
||||
|
@ -5596,6 +5703,7 @@ impl Editor {
|
|||
if let Some(entry) = self.selection_history.undo_stack.pop_back() {
|
||||
self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
|
||||
self.select_next_state = entry.select_next_state;
|
||||
self.select_prev_state = entry.select_prev_state;
|
||||
self.add_selections_state = entry.add_selections_state;
|
||||
self.request_autoscroll(Autoscroll::newest(), cx);
|
||||
}
|
||||
|
@ -5608,6 +5716,7 @@ impl Editor {
|
|||
if let Some(entry) = self.selection_history.redo_stack.pop_back() {
|
||||
self.change_selections(None, cx, |s| s.select_anchors(entry.selections.to_vec()));
|
||||
self.select_next_state = entry.select_next_state;
|
||||
self.select_prev_state = entry.select_prev_state;
|
||||
self.add_selections_state = entry.add_selections_state;
|
||||
self.request_autoscroll(Autoscroll::newest(), cx);
|
||||
}
|
||||
|
@ -6385,6 +6494,7 @@ impl Editor {
|
|||
self.selection_history.push(SelectionHistoryEntry {
|
||||
selections: self.selections.disjoint_anchors(),
|
||||
select_next_state: self.select_next_state.clone(),
|
||||
select_prev_state: self.select_prev_state.clone(),
|
||||
add_selections_state: self.add_selections_state.clone(),
|
||||
});
|
||||
}
|
||||
|
@ -7130,15 +7240,6 @@ impl Editor {
|
|||
.show_copilot_suggestions;
|
||||
|
||||
let telemetry = project.read(cx).client().telemetry().clone();
|
||||
telemetry.report_mixpanel_event(
|
||||
match name {
|
||||
"open" => "open editor",
|
||||
"save" => "save editor",
|
||||
_ => name,
|
||||
},
|
||||
json!({ "File Extension": file_extension, "Vim Mode": vim_mode, "In Clickhouse": true }),
|
||||
telemetry_settings,
|
||||
);
|
||||
let event = ClickhouseEvent::Editor {
|
||||
file_extension,
|
||||
vim_mode,
|
||||
|
@ -7836,13 +7937,13 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
|
|||
}
|
||||
|
||||
pub fn highlight_diagnostic_message(
|
||||
inital_highlights: Vec<usize>,
|
||||
initial_highlights: Vec<usize>,
|
||||
message: &str,
|
||||
) -> (String, Vec<usize>) {
|
||||
let mut message_without_backticks = String::new();
|
||||
let mut prev_offset = 0;
|
||||
let mut inside_block = false;
|
||||
let mut highlights = inital_highlights;
|
||||
let mut highlights = initial_highlights;
|
||||
for (match_ix, (offset, _)) in message
|
||||
.match_indices('`')
|
||||
.chain([(message.len(), "")])
|
||||
|
|
|
@ -9,7 +9,8 @@ use gpui::{
|
|||
executor::Deterministic,
|
||||
geometry::{rect::RectF, vector::vec2f},
|
||||
platform::{WindowBounds, WindowOptions},
|
||||
serde_json, TestAppContext,
|
||||
serde_json::{self, json},
|
||||
TestAppContext,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
|
@ -3107,6 +3108,57 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) {
|
|||
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_previous(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
{
|
||||
// `Select previous` without a selection (selects wordwise)
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("abc\nˇabc abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
|
||||
cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
|
||||
}
|
||||
{
|
||||
// `Select previous` with a selection
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
|
||||
|
||||
cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
|
||||
cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -4270,7 +4322,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
// Set rust language override and assert overriden tabsize is sent to language server
|
||||
// Set rust language override and assert overridden tabsize is sent to language server
|
||||
update_test_settings(cx, |settings| {
|
||||
settings.languages.insert(
|
||||
"Rust".into(),
|
||||
|
@ -4384,7 +4436,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
assert!(!cx.read(|cx| editor.is_dirty(cx)));
|
||||
|
||||
// Set rust language override and assert overriden tabsize is sent to language server
|
||||
// Set rust language override and assert overridden tabsize is sent to language server
|
||||
update_test_settings(cx, |settings| {
|
||||
settings.languages.insert(
|
||||
"Rust".into(),
|
||||
|
@ -4725,7 +4777,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
|||
two
|
||||
threeˇ
|
||||
"},
|
||||
"overlapping aditional edit",
|
||||
"overlapping additional edit",
|
||||
),
|
||||
(
|
||||
indoc! {"
|
||||
|
|
|
@ -3115,7 +3115,7 @@ mod tests {
|
|||
editor_width: f32,
|
||||
) -> Vec<Invisible> {
|
||||
info!(
|
||||
"Creating editor with mode {editor_mode:?}, witdh {editor_width} and text '{input_text}'"
|
||||
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
|
||||
);
|
||||
let (_, editor) = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&input_text, cx);
|
||||
|
|
|
@ -199,6 +199,13 @@ pub struct MultiBufferBytes<'a> {
|
|||
chunk: &'a [u8],
|
||||
}
|
||||
|
||||
pub struct ReversedMultiBufferBytes<'a> {
|
||||
range: Range<usize>,
|
||||
excerpts: Cursor<'a, Excerpt, usize>,
|
||||
excerpt_bytes: Option<ExcerptBytes<'a>>,
|
||||
chunk: &'a [u8],
|
||||
}
|
||||
|
||||
struct ExcerptChunks<'a> {
|
||||
content_chunks: BufferChunks<'a>,
|
||||
footer_height: usize,
|
||||
|
@ -1978,7 +1985,6 @@ impl MultiBufferSnapshot {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
MultiBufferBytes {
|
||||
range,
|
||||
excerpts,
|
||||
|
@ -1987,6 +1993,33 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn reversed_bytes_in_range<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> ReversedMultiBufferBytes {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut excerpts = self.excerpts.cursor::<usize>();
|
||||
excerpts.seek(&range.end, Bias::Left, &());
|
||||
|
||||
let mut chunk = &[][..];
|
||||
let excerpt_bytes = if let Some(excerpt) = excerpts.item() {
|
||||
let mut excerpt_bytes = excerpt.reversed_bytes_in_range(
|
||||
range.start - excerpts.start()..range.end - excerpts.start(),
|
||||
);
|
||||
chunk = excerpt_bytes.next().unwrap_or(&[][..]);
|
||||
Some(excerpt_bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ReversedMultiBufferBytes {
|
||||
range,
|
||||
excerpts,
|
||||
excerpt_bytes,
|
||||
chunk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows {
|
||||
let mut result = MultiBufferRows {
|
||||
buffer_row_range: 0..0,
|
||||
|
@ -3420,6 +3453,26 @@ impl Excerpt {
|
|||
}
|
||||
}
|
||||
|
||||
fn reversed_bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
|
||||
let content_start = self.range.context.start.to_offset(&self.buffer);
|
||||
let bytes_start = content_start + range.start;
|
||||
let bytes_end = content_start + cmp::min(range.end, self.text_summary.len);
|
||||
let footer_height = if self.has_trailing_newline
|
||||
&& range.start <= self.text_summary.len
|
||||
&& range.end > self.text_summary.len
|
||||
{
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let content_bytes = self.buffer.reversed_bytes_in_range(bytes_start..bytes_end);
|
||||
|
||||
ExcerptBytes {
|
||||
content_bytes,
|
||||
footer_height,
|
||||
}
|
||||
}
|
||||
|
||||
fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor {
|
||||
if text_anchor
|
||||
.cmp(&self.range.context.start, &self.buffer)
|
||||
|
@ -3738,6 +3791,38 @@ impl<'a> io::Read for MultiBufferBytes<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ReversedMultiBufferBytes<'a> {
|
||||
fn consume(&mut self, len: usize) {
|
||||
self.range.end -= len;
|
||||
self.chunk = &self.chunk[..self.chunk.len() - len];
|
||||
|
||||
if !self.range.is_empty() && self.chunk.is_empty() {
|
||||
if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) {
|
||||
self.chunk = chunk;
|
||||
} else {
|
||||
self.excerpts.next(&());
|
||||
if let Some(excerpt) = self.excerpts.item() {
|
||||
let mut excerpt_bytes =
|
||||
excerpt.bytes_in_range(0..self.range.end - self.excerpts.start());
|
||||
self.chunk = excerpt_bytes.next().unwrap();
|
||||
self.excerpt_bytes = Some(excerpt_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> io::Read for ReversedMultiBufferBytes<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let len = cmp::min(buf.len(), self.chunk.len());
|
||||
buf[..len].copy_from_slice(&self.chunk[..len]);
|
||||
buf[..len].reverse();
|
||||
if len > 0 {
|
||||
self.consume(len);
|
||||
}
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for ExcerptBytes<'a> {
|
||||
type Item = &'a [u8];
|
||||
|
||||
|
@ -5258,7 +5343,7 @@ mod tests {
|
|||
assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678");
|
||||
|
||||
// An undo in the multibuffer undoes the multibuffer transaction
|
||||
// and also any individual buffer edits that have occured since
|
||||
// and also any individual buffer edits that have occurred since
|
||||
// that transaction.
|
||||
multibuffer.undo(cx);
|
||||
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
||||
|
|
|
@ -48,8 +48,8 @@ pub fn marked_display_snapshot(
|
|||
}
|
||||
|
||||
pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
|
||||
let (umarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
assert_eq!(editor.text(cx), umarked_text);
|
||||
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ use std::{env, fmt::Display};
|
|||
use sysinfo::{System, SystemExt};
|
||||
use util::channel::ReleaseChannel;
|
||||
|
||||
// TODO: Move this file out of feedback and into a more general place
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct SystemSpecs {
|
||||
#[serde(serialize_with = "serialize_app_version")]
|
||||
|
|
|
@ -32,7 +32,7 @@ use repository::{FakeGitRepositoryState, GitFileStatus};
|
|||
use std::sync::Weak;
|
||||
|
||||
lazy_static! {
|
||||
static ref LINE_SEPERATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap();
|
||||
static ref LINE_SEPARATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -77,13 +77,13 @@ impl LineEnding {
|
|||
}
|
||||
|
||||
pub fn normalize(text: &mut String) {
|
||||
if let Cow::Owned(replaced) = LINE_SEPERATORS_REGEX.replace_all(text, "\n") {
|
||||
if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") {
|
||||
*text = replaced;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
|
||||
if let Cow::Owned(replaced) = LINE_SEPERATORS_REGEX.replace_all(&text, "\n") {
|
||||
if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
|
||||
replaced.into()
|
||||
} else {
|
||||
text
|
||||
|
|
|
@ -53,7 +53,7 @@ uuid = { version = "1.1.2", features = ["v4"] }
|
|||
waker-fn = "1.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.59.2"
|
||||
bindgen = "0.65.1"
|
||||
cc = "1.0.67"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -6335,9 +6335,9 @@ mod tests {
|
|||
#[crate::test(self)]
|
||||
async fn test_labeled_tasks(cx: &mut TestAppContext) {
|
||||
assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
|
||||
let (mut sender, mut reciever) = postage::oneshot::channel::<()>();
|
||||
let (mut sender, mut receiver) = postage::oneshot::channel::<()>();
|
||||
let task = cx
|
||||
.update(|cx| cx.spawn_labeled("Test Label", |_| async move { reciever.recv().await }));
|
||||
.update(|cx| cx.spawn_labeled("Test Label", |_| async move { receiver.recv().await }));
|
||||
|
||||
assert_eq!(
|
||||
Some("Test Label"),
|
||||
|
|
|
@ -965,10 +965,10 @@ impl<'a> WindowContext<'a> {
|
|||
}
|
||||
|
||||
pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||
let root_view_id = self.window.root_view().id();
|
||||
let focused_view_id = self.window.focused_view_id?;
|
||||
self.window
|
||||
.rendered_views
|
||||
.get(&root_view_id)?
|
||||
.get(&focused_view_id)?
|
||||
.rect_for_text_range(range_utf16, self)
|
||||
.log_err()
|
||||
.flatten()
|
||||
|
|
|
@ -84,8 +84,8 @@ impl InputHandler for WindowInputHandler {
|
|||
|
||||
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
|
||||
.borrow()
|
||||
.read_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ impl KeymapMatcher {
|
|||
/// MatchResult::Pending =>
|
||||
/// There exist bindings which are still waiting for more keys.
|
||||
/// MatchResult::Complete(matches) =>
|
||||
/// 1 or more bindings have recieved the necessary key presses.
|
||||
/// 1 or more bindings have received the necessary key presses.
|
||||
/// The order of the matched actions is by position of the matching first,
|
||||
// and order in the keymap second.
|
||||
pub fn push_keystroke(
|
||||
|
|
|
@ -264,7 +264,7 @@ impl settings::Setting for AllLanguageSettings {
|
|||
let mut root_schema = generator.root_schema_for::<Self::FileContent>();
|
||||
|
||||
// Create a schema for a 'languages overrides' object, associating editor
|
||||
// settings with specific langauges.
|
||||
// settings with specific languages.
|
||||
assert!(root_schema
|
||||
.definitions
|
||||
.contains_key("LanguageSettingsContent"));
|
||||
|
|
|
@ -773,7 +773,7 @@ impl<'a> SyntaxMapCaptures<'a> {
|
|||
} in layers
|
||||
{
|
||||
let grammar = match &language.grammar {
|
||||
Some(grammer) => grammer,
|
||||
Some(grammar) => grammar,
|
||||
None => continue,
|
||||
};
|
||||
let query = match query(&grammar) {
|
||||
|
@ -896,7 +896,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
|||
} in layers
|
||||
{
|
||||
let grammar = match &language.grammar {
|
||||
Some(grammer) => grammer,
|
||||
Some(grammar) => grammar,
|
||||
None => continue,
|
||||
};
|
||||
let query = match query(&grammar) {
|
||||
|
|
|
@ -18,4 +18,4 @@ metal = "0.21.0"
|
|||
objc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.59.2"
|
||||
bindgen = "0.65.1"
|
||||
|
|
|
@ -11,7 +11,7 @@ use syn::{parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Ty
|
|||
/// "Hello from Wasm".into()
|
||||
/// }
|
||||
/// ```
|
||||
/// This macro makes a function defined guest-side avaliable host-side.
|
||||
/// This macro makes a function defined guest-side available host-side.
|
||||
/// Note that all arguments and return types must be `serde`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
|
@ -92,7 +92,7 @@ pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
|
|||
/// #[import]
|
||||
/// pub fn operating_system_name() -> String;
|
||||
/// ```
|
||||
/// This macro makes a function defined host-side avaliable guest-side.
|
||||
/// This macro makes a function defined host-side available guest-side.
|
||||
/// Note that all arguments and return types must be `serde`.
|
||||
/// All that's provided is a signature, as the function is implemented host-side.
|
||||
#[proc_macro_attribute]
|
||||
|
|
|
@ -127,7 +127,7 @@ use plugin_handles::RopeHandle;
|
|||
pub fn append(rope: RopeHandle, string: &str);
|
||||
```
|
||||
|
||||
This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only aquire resources to handles we're given, so we'd need to expose a fuction that takes a handle.
|
||||
This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle.
|
||||
|
||||
To illustrate that point, here's an example. First, we'd define a plugin-side function as follows:
|
||||
|
||||
|
@ -177,7 +177,7 @@ So here's what calling `append_newline` would do, from the top:
|
|||
|
||||
6. And from here on out we return up the callstack, through Wasm, to Rust all the way back to where we started. Right before we return, we clear out the `ResourcePool`, so that we're no longer holding onto the underlying resource.
|
||||
|
||||
Throughout this entire chain of calls, the resource remain host-side. By temporarilty checking it into a `ResourcePool`, we're able to keep a reference to the resource that we can use, while avoiding copying the uncopyable resource.
|
||||
Throughout this entire chain of calls, the resource remain host-side. By temporarily checking it into a `ResourcePool`, we're able to keep a reference to the resource that we can use, while avoiding copying the uncopyable resource.
|
||||
|
||||
## Final Notes
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ impl PluginBuilder {
|
|||
"env",
|
||||
&format!("__{}", name),
|
||||
move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| {
|
||||
// TODO: use try block once avaliable
|
||||
// TODO: use try block once available
|
||||
let result: Result<(WasiBuffer, Memory, _), Trap> = (|| {
|
||||
// grab a handle to the memory
|
||||
let plugin_memory = match caller.get_export("memory") {
|
||||
|
@ -211,7 +211,7 @@ impl PluginBuilder {
|
|||
"env",
|
||||
&format!("__{}", name),
|
||||
move |mut caller: Caller<'_, WasiCtxAlloc>, packed_buffer: u64| {
|
||||
// TODO: use try block once avaliable
|
||||
// TODO: use try block once available
|
||||
let result: Result<(WasiBuffer, Memory, Vec<u8>), Trap> = (|| {
|
||||
// grab a handle to the memory
|
||||
let plugin_memory = match caller.get_export("memory") {
|
||||
|
@ -297,7 +297,7 @@ pub enum PluginBinary<'a> {
|
|||
Precompiled(&'a [u8]),
|
||||
}
|
||||
|
||||
/// Represents a WebAssembly plugin, with access to the WebAssembly System Inferface.
|
||||
/// Represents a WebAssembly plugin, with access to the WebAssembly System Interface.
|
||||
/// Build a new plugin using [`PluginBuilder`].
|
||||
pub struct Plugin {
|
||||
store: Store<WasiCtxAlloc>,
|
||||
|
@ -559,7 +559,7 @@ impl Plugin {
|
|||
.ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?;
|
||||
|
||||
// write the argument to linear memory
|
||||
// this returns a (ptr, lentgh) pair
|
||||
// this returns a (ptr, length) pair
|
||||
let arg_buffer = Self::bytes_to_buffer(
|
||||
self.store.data().alloc_buffer(),
|
||||
&mut plugin_memory,
|
||||
|
@ -569,7 +569,7 @@ impl Plugin {
|
|||
.await?;
|
||||
|
||||
// call the function, passing in the buffer and its length
|
||||
// this returns a ptr to a (ptr, lentgh) pair
|
||||
// this returns a ptr to a (ptr, length) pair
|
||||
let result_buffer = handle
|
||||
.function
|
||||
.call_async(&mut self.store, arg_buffer.into_u64())
|
||||
|
|
|
@ -4052,7 +4052,7 @@ impl Project {
|
|||
let end_within = range.start.cmp(&primary.end, buffer).is_le()
|
||||
&& range.end.cmp(&primary.end, buffer).is_ge();
|
||||
|
||||
//Skip addtional edits which overlap with the primary completion edit
|
||||
//Skip additional edits which overlap with the primary completion edit
|
||||
//https://github.com/zed-industries/zed/pull/1871
|
||||
if !start_within && !end_within {
|
||||
buffer.edit([(range, text)], None, cx);
|
||||
|
|
|
@ -157,7 +157,7 @@ impl RepositoryEntry {
|
|||
self.statuses
|
||||
.iter_from(&repo_path)
|
||||
.take_while(|(key, _)| key.starts_with(&repo_path))
|
||||
// Short circut once we've found the highest level
|
||||
// Short circuit once we've found the highest level
|
||||
.take_until(|(_, status)| status == &&GitFileStatus::Conflict)
|
||||
.map(|(_, status)| status)
|
||||
.reduce(
|
||||
|
@ -3623,7 +3623,7 @@ pub trait WorktreeHandle {
|
|||
|
||||
impl WorktreeHandle for ModelHandle<Worktree> {
|
||||
// When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that
|
||||
// occurred before the worktree was constructed. These events can cause the worktree to perfrom
|
||||
// occurred before the worktree was constructed. These events can cause the worktree to perform
|
||||
// extra directory scans, and emit extra scan-state notifications.
|
||||
//
|
||||
// This function mutates the worktree's directory and waits for those mutations to be picked up,
|
||||
|
|
|
@ -276,7 +276,7 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
// Set up fake langauge server to return fuzzy matches against
|
||||
// Set up fake language server to return fuzzy matches against
|
||||
// a fixed set of symbol names.
|
||||
let fake_symbols = [
|
||||
symbol("one", "/external"),
|
||||
|
|
|
@ -179,7 +179,11 @@ impl Rope {
|
|||
}
|
||||
|
||||
pub fn bytes_in_range(&self, range: Range<usize>) -> Bytes {
|
||||
Bytes::new(self, range)
|
||||
Bytes::new(self, range, false)
|
||||
}
|
||||
|
||||
pub fn reversed_bytes_in_range(&self, range: Range<usize>) -> Bytes {
|
||||
Bytes::new(self, range, true)
|
||||
}
|
||||
|
||||
pub fn chunks(&self) -> Chunks {
|
||||
|
@ -579,22 +583,33 @@ impl<'a> Iterator for Chunks<'a> {
|
|||
pub struct Bytes<'a> {
|
||||
chunks: sum_tree::Cursor<'a, Chunk, usize>,
|
||||
range: Range<usize>,
|
||||
reversed: bool,
|
||||
}
|
||||
|
||||
impl<'a> Bytes<'a> {
|
||||
pub fn new(rope: &'a Rope, range: Range<usize>) -> Self {
|
||||
pub fn new(rope: &'a Rope, range: Range<usize>, reversed: bool) -> Self {
|
||||
let mut chunks = rope.chunks.cursor();
|
||||
chunks.seek(&range.start, Bias::Right, &());
|
||||
Self { chunks, range }
|
||||
if reversed {
|
||||
chunks.seek(&range.end, Bias::Left, &());
|
||||
} else {
|
||||
chunks.seek(&range.start, Bias::Right, &());
|
||||
}
|
||||
Self {
|
||||
chunks,
|
||||
range,
|
||||
reversed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Option<&'a [u8]> {
|
||||
let chunk = self.chunks.item()?;
|
||||
if self.reversed && self.range.start >= self.chunks.end(&()) {
|
||||
return None;
|
||||
}
|
||||
let chunk_start = *self.chunks.start();
|
||||
if self.range.end <= chunk_start {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.range.start.saturating_sub(chunk_start);
|
||||
let end = self.range.end - chunk_start;
|
||||
Some(&chunk.0.as_bytes()[start..chunk.0.len().min(end)])
|
||||
|
@ -607,7 +622,11 @@ impl<'a> Iterator for Bytes<'a> {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result = self.peek();
|
||||
if result.is_some() {
|
||||
self.chunks.next(&());
|
||||
if self.reversed {
|
||||
self.chunks.prev(&());
|
||||
} else {
|
||||
self.chunks.next(&());
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -617,10 +636,21 @@ impl<'a> io::Read for Bytes<'a> {
|
|||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if let Some(chunk) = self.peek() {
|
||||
let len = cmp::min(buf.len(), chunk.len());
|
||||
buf[..len].copy_from_slice(&chunk[..len]);
|
||||
self.range.start += len;
|
||||
if self.reversed {
|
||||
buf[..len].copy_from_slice(&chunk[chunk.len() - len..]);
|
||||
buf[..len].reverse();
|
||||
self.range.end -= len;
|
||||
} else {
|
||||
buf[..len].copy_from_slice(&chunk[..len]);
|
||||
self.range.start += len;
|
||||
}
|
||||
|
||||
if len == chunk.len() {
|
||||
self.chunks.next(&());
|
||||
if self.reversed {
|
||||
self.chunks.prev(&());
|
||||
} else {
|
||||
self.chunks.next(&());
|
||||
}
|
||||
}
|
||||
Ok(len)
|
||||
} else {
|
||||
|
|
|
@ -476,7 +476,7 @@ message Symbol {
|
|||
string name = 4;
|
||||
int32 kind = 5;
|
||||
string path = 6;
|
||||
// Cannot use generate anchors for unopend files,
|
||||
// Cannot use generate anchors for unopened files,
|
||||
// so we are forced to use point coords instead
|
||||
PointUtf16 start = 7;
|
||||
PointUtf16 end = 8;
|
||||
|
|
|
@ -42,7 +42,7 @@ impl PublicKey {
|
|||
}
|
||||
|
||||
impl PrivateKey {
|
||||
/// Decrypt a base64-encoded string that was encrypted by the correspoding public key.
|
||||
/// Decrypt a base64-encoded string that was encrypted by the corresponding public key.
|
||||
pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
|
||||
let encrypted_bytes = base64::decode_config(encrypted_string, base64::URL_SAFE)
|
||||
.context("failed to base64-decode encrypted string")?;
|
||||
|
|
|
@ -25,7 +25,7 @@ use std::{
|
|||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
mem,
|
||||
ops::Range,
|
||||
ops::{Not, Range},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -242,7 +242,13 @@ impl View for ProjectSearchView {
|
|||
|
||||
impl Item for ProjectSearchView {
|
||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
|
||||
Some(self.query_editor.read(cx).text(cx).into())
|
||||
let query_text = self.query_editor.read(cx).text(cx);
|
||||
|
||||
query_text
|
||||
.is_empty()
|
||||
.not()
|
||||
.then(|| query_text.into())
|
||||
.or_else(|| Some("Project Search".into()))
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
|
|
|
@ -9,10 +9,23 @@ pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
|
|||
use std::{borrow::Cow, str};
|
||||
|
||||
pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
|
||||
pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
|
||||
const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
|
||||
const INITIAL_LOCAL_SETTINGS_ASSET_PATH: &str = "settings/initial_local_settings.json";
|
||||
|
||||
pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
|
||||
match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
|
||||
pub fn default_settings() -> Cow<'static, str> {
|
||||
asset_str(&assets::Assets, DEFAULT_SETTINGS_ASSET_PATH)
|
||||
}
|
||||
|
||||
pub fn initial_user_settings_content(assets: &dyn AssetSource) -> Cow<'_, str> {
|
||||
asset_str(assets, INITIAL_USER_SETTINGS_ASSET_PATH)
|
||||
}
|
||||
|
||||
pub fn initial_local_settings_content(assets: &dyn AssetSource) -> Cow<'_, str> {
|
||||
asset_str(assets, INITIAL_LOCAL_SETTINGS_ASSET_PATH)
|
||||
}
|
||||
|
||||
fn asset_str<'a>(assets: &'a dyn AssetSource, path: &str) -> Cow<'a, str> {
|
||||
match assets.load(path).unwrap() {
|
||||
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
||||
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH};
|
||||
use crate::{settings_store::SettingsStore, Setting};
|
||||
use anyhow::Result;
|
||||
use assets::Assets;
|
||||
use fs::Fs;
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use gpui::{executor::Background, AppContext, AssetSource};
|
||||
use gpui::{executor::Background, AppContext};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::ErrorKind,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
|
@ -28,19 +27,12 @@ pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppCo
|
|||
cx.global::<SettingsStore>().get(location)
|
||||
}
|
||||
|
||||
pub fn default_settings() -> Cow<'static, str> {
|
||||
match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
|
||||
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
||||
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
||||
}
|
||||
}
|
||||
|
||||
pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test_settings() -> String {
|
||||
let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
|
||||
default_settings().as_ref(),
|
||||
crate::default_settings().as_ref(),
|
||||
)
|
||||
.unwrap();
|
||||
util::merge_non_null_json_value_into(
|
||||
|
|
|
@ -623,22 +623,6 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// impl Debug for SettingsStore {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// return f
|
||||
// .debug_struct("SettingsStore")
|
||||
// .field(
|
||||
// "setting_value_sets_by_type",
|
||||
// &self
|
||||
// .setting_values
|
||||
// .values()
|
||||
// .map(|set| (set.setting_type_name(), set))
|
||||
// .collect::<HashMap<_, _>>(),
|
||||
// )
|
||||
// .finish_non_exhaustive();
|
||||
// }
|
||||
// }
|
||||
|
||||
fn update_value_in_json_text<'a>(
|
||||
text: &mut String,
|
||||
key_path: &mut Vec<&'a str>,
|
||||
|
@ -681,6 +665,10 @@ fn update_value_in_json_text<'a>(
|
|||
key_path.pop();
|
||||
}
|
||||
} else if old_value != new_value {
|
||||
let mut new_value = new_value.clone();
|
||||
if let Some(new_object) = new_value.as_object_mut() {
|
||||
new_object.retain(|_, v| !v.is_null());
|
||||
}
|
||||
let (range, replacement) =
|
||||
replace_value_in_json_text(text, &key_path, tab_size, &new_value);
|
||||
text.replace_range(range.clone(), &replacement);
|
||||
|
@ -692,7 +680,7 @@ fn replace_value_in_json_text(
|
|||
text: &str,
|
||||
key_path: &[&str],
|
||||
tab_size: usize,
|
||||
new_value: impl Serialize,
|
||||
new_value: &serde_json::Value,
|
||||
) -> (Range<usize>, String) {
|
||||
const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
|
||||
const LANGUAGES: &'static str = "languages";
|
||||
|
@ -1039,24 +1027,32 @@ mod tests {
|
|||
r#"{
|
||||
"languages": {
|
||||
"JSON": {
|
||||
"is_enabled": true
|
||||
"language_setting_1": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
|settings| {
|
||||
settings.languages.get_mut("JSON").unwrap().is_enabled = false;
|
||||
settings
|
||||
.languages
|
||||
.insert("Rust".into(), LanguageSettingEntry { is_enabled: true });
|
||||
.get_mut("JSON")
|
||||
.unwrap()
|
||||
.language_setting_1 = Some(false);
|
||||
settings.languages.insert(
|
||||
"Rust".into(),
|
||||
LanguageSettingEntry {
|
||||
language_setting_2: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
},
|
||||
r#"{
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"is_enabled": true
|
||||
"language_setting_2": true
|
||||
},
|
||||
"JSON": {
|
||||
"is_enabled": false
|
||||
"language_setting_1": false
|
||||
}
|
||||
}
|
||||
}"#
|
||||
|
@ -1119,6 +1115,23 @@ mod tests {
|
|||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
check_settings_update::<UserSettings>(
|
||||
&mut store,
|
||||
r#"{
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
|settings| settings.age = Some(37),
|
||||
r#"{
|
||||
"user": {
|
||||
"age": 37
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_settings_update<T: Setting>(
|
||||
|
@ -1247,9 +1260,10 @@ mod tests {
|
|||
languages: HashMap<String, LanguageSettingEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
struct LanguageSettingEntry {
|
||||
is_enabled: bool,
|
||||
language_setting_1: Option<bool>,
|
||||
language_setting_2: Option<bool>,
|
||||
}
|
||||
|
||||
impl Setting for LanguageSettings {
|
||||
|
|
|
@ -160,7 +160,7 @@ impl<M: Migrator> ThreadSafeConnection<M> {
|
|||
|
||||
// Create a one shot channel for the result of the queued write
|
||||
// so we can await on the result
|
||||
let (sender, reciever) = oneshot::channel();
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
|
||||
let thread_safe_connection = (*self).clone();
|
||||
write_channel(Box::new(move || {
|
||||
|
@ -168,7 +168,7 @@ impl<M: Migrator> ThreadSafeConnection<M> {
|
|||
let result = connection.with_write(|connection| callback(connection));
|
||||
sender.send(result).ok();
|
||||
}));
|
||||
reciever.map(|response| response.expect("Write queue unexpectedly closed"))
|
||||
receiver.map(|response| response.expect("Write queue unexpectedly closed"))
|
||||
}
|
||||
|
||||
pub(crate) fn create_connection(
|
||||
|
@ -245,10 +245,10 @@ pub fn background_thread_queue() -> WriteQueueConstructor {
|
|||
use std::sync::mpsc::channel;
|
||||
|
||||
Box::new(|| {
|
||||
let (sender, reciever) = channel::<QueuedWrite>();
|
||||
let (sender, receiver) = channel::<QueuedWrite>();
|
||||
|
||||
thread::spawn(move || {
|
||||
while let Ok(write) = reciever.recv() {
|
||||
while let Ok(write) = receiver.recv() {
|
||||
write()
|
||||
}
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
|
|||
}
|
||||
|
||||
///Converts an 8 bit ANSI color to it's GPUI equivalent.
|
||||
///Accepts usize for compatability with the alacritty::Colors interface,
|
||||
///Accepts usize for compatibility with the alacritty::Colors interface,
|
||||
///Other than that use case, should only be called with values in the [0,255] range
|
||||
pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
|
||||
match index {
|
||||
|
@ -78,7 +78,7 @@ pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
|
|||
let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
|
||||
Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
|
||||
}
|
||||
//For compatability with the alacritty::Colors interface
|
||||
//For compatibility with the alacritty::Colors interface
|
||||
256 => style.foreground,
|
||||
257 => style.background,
|
||||
258 => style.cursor,
|
||||
|
|
|
@ -18,6 +18,6 @@ There are currently many distinct paths for getting keystrokes to the terminal:
|
|||
|
||||
3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`.
|
||||
|
||||
4. Pasted text has a seperate pathway.
|
||||
4. Pasted text has a separate pathway.
|
||||
|
||||
Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the `.input()` API (which try_keystroke uses) so we have consistent input handling across the terminal
|
|
@ -40,7 +40,7 @@ function contrast_colour {
|
|||
|
||||
# Uncomment the below for more precise luminance calculations
|
||||
|
||||
# # Calculate percieved brightness
|
||||
# # Calculate perceived brightness
|
||||
# # See https://www.w3.org/TR/AERT#color-contrast
|
||||
# # and http://www.itu.int/rec/R-REC-BT.601
|
||||
# # Luminance is in range 0..5000 as each value is 0..5
|
||||
|
|
|
@ -34,7 +34,7 @@ use std::{mem, ops::Range};
|
|||
|
||||
use crate::TerminalView;
|
||||
|
||||
///The information generated during layout that is nescessary for painting
|
||||
///The information generated during layout that is necessary for painting
|
||||
pub struct LayoutState {
|
||||
cells: Vec<LayoutCell>,
|
||||
rects: Vec<LayoutRect>,
|
||||
|
@ -206,7 +206,7 @@ impl TerminalElement {
|
|||
//Expand background rect range
|
||||
{
|
||||
if matches!(bg, Named(NamedColor::Background)) {
|
||||
//Continue to next cell, resetting variables if nescessary
|
||||
//Continue to next cell, resetting variables if necessary
|
||||
cur_alac_color = None;
|
||||
if let Some(rect) = cur_rect {
|
||||
rects.push(rect);
|
||||
|
|
|
@ -804,7 +804,7 @@ mod tests {
|
|||
let workspace = workspace.read(cx);
|
||||
let active_entry = project.read(cx).active_entry();
|
||||
|
||||
//Make sure enviroment is as expeted
|
||||
//Make sure environment is as expected
|
||||
assert!(active_entry.is_none());
|
||||
assert!(workspace.worktrees(cx).next().is_none());
|
||||
|
||||
|
@ -825,7 +825,7 @@ mod tests {
|
|||
let workspace = workspace.read(cx);
|
||||
let active_entry = project.read(cx).active_entry();
|
||||
|
||||
//Make sure enviroment is as expeted
|
||||
//Make sure environment is as expected
|
||||
assert!(active_entry.is_none());
|
||||
assert!(workspace.worktrees(cx).next().is_some());
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ fn test_line_len() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_common_prefix_at_positionn() {
|
||||
fn test_common_prefix_at_position() {
|
||||
let text = "a = str; b = δα";
|
||||
let buffer = Buffer::new(0, 0, text.into());
|
||||
|
||||
|
@ -216,7 +216,7 @@ fn test_common_prefix_at_positionn() {
|
|||
empty_range_after(text, "str"),
|
||||
);
|
||||
|
||||
// prefix matching is case insenstive.
|
||||
// prefix matching is case insensitive.
|
||||
assert_eq!(
|
||||
buffer.common_prefix_at(offset1, "Strαngε"),
|
||||
range_of(text, "str"),
|
||||
|
|
|
@ -1749,6 +1749,12 @@ impl BufferSnapshot {
|
|||
self.visible_text.bytes_in_range(start..end)
|
||||
}
|
||||
|
||||
pub fn reversed_bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> rope::Bytes<'_> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
self.visible_text.reversed_bytes_in_range(start..end)
|
||||
}
|
||||
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks<'_> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod test;
|
|||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
ops::{AddAssign, Range, RangeInclusive},
|
||||
panic::Location,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
@ -129,11 +130,13 @@ where
|
|||
{
|
||||
type Ok = T;
|
||||
|
||||
#[track_caller]
|
||||
fn log_err(self) -> Option<T> {
|
||||
match self {
|
||||
Ok(value) => Some(value),
|
||||
Err(error) => {
|
||||
log::error!("{:?}", error);
|
||||
let caller = Location::caller();
|
||||
log::error!("{}:{}: {:?}", caller.file(), caller.line(), error);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -756,7 +756,7 @@ mod test {
|
|||
ˇ
|
||||
The quick"})
|
||||
.await;
|
||||
// Indoc disallows trailing whitspace.
|
||||
// Indoc disallows trailing whitespace.
|
||||
cx.assert(" ˇ \nThe quick").await;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ use tokio::{
|
|||
use crate::state::Mode;
|
||||
use collections::VecDeque;
|
||||
|
||||
// Neovim doesn't like to be started simultaneously from multiple threads. We use thsi lock
|
||||
// Neovim doesn't like to be started simultaneously from multiple threads. We use this lock
|
||||
// to ensure we are only constructing one neovim connection at a time.
|
||||
#[cfg(feature = "neovim")]
|
||||
lazy_static! {
|
||||
|
|
|
@ -1022,7 +1022,7 @@ impl Pane {
|
|||
let is_active_item = target_item_id == active_item_id;
|
||||
let target_pane = cx.weak_handle();
|
||||
|
||||
// The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currenlty, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
|
||||
// The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
|
||||
|
||||
self.tab_context_menu.update(cx, |menu, cx| {
|
||||
menu.show(
|
||||
|
|
|
@ -15,7 +15,6 @@ mod toolbar;
|
|||
mod workspace_settings;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assets::Assets;
|
||||
use call::ActiveCall;
|
||||
use client::{
|
||||
proto::{self, PeerId},
|
||||
|
@ -83,7 +82,7 @@ use status_bar::StatusBar;
|
|||
pub use status_bar::StatusItemView;
|
||||
use theme::{Theme, ThemeSettings};
|
||||
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
||||
use util::{async_iife, paths, ResultExt};
|
||||
use util::{async_iife, ResultExt};
|
||||
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -133,8 +132,6 @@ actions!(
|
|||
]
|
||||
);
|
||||
|
||||
actions!(zed, [OpenSettings]);
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct OpenPaths {
|
||||
pub paths: Vec<PathBuf>,
|
||||
|
@ -295,17 +292,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
.detach();
|
||||
});
|
||||
|
||||
cx.add_action(
|
||||
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||
settings::initial_user_settings_content(&Assets)
|
||||
.as_ref()
|
||||
.into()
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
},
|
||||
);
|
||||
|
||||
let client = &app_state.client;
|
||||
client.add_view_request_handler(Workspace::handle_follow);
|
||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||
|
@ -765,25 +751,21 @@ impl Workspace {
|
|||
DB.next_id().await.unwrap_or(0)
|
||||
};
|
||||
|
||||
let window_bounds_override =
|
||||
ZED_WINDOW_POSITION
|
||||
.zip(*ZED_WINDOW_SIZE)
|
||||
.map(|(position, size)| {
|
||||
WindowBounds::Fixed(RectF::new(
|
||||
cx.platform().screens()[0].bounds().origin() + position,
|
||||
size,
|
||||
))
|
||||
});
|
||||
|
||||
let build_workspace = |cx: &mut ViewContext<Workspace>| {
|
||||
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
|
||||
};
|
||||
|
||||
let workspace = requesting_window_id
|
||||
.and_then(|window_id| {
|
||||
cx.update(|cx| cx.replace_root_view(window_id, |cx| build_workspace(cx)))
|
||||
cx.update(|cx| {
|
||||
cx.replace_root_view(window_id, |cx| {
|
||||
Workspace::new(
|
||||
workspace_id,
|
||||
project_handle.clone(),
|
||||
app_state.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let window_bounds_override = window_bounds_env_override(&cx);
|
||||
let (bounds, display) = if let Some(bounds) = window_bounds_override {
|
||||
(Some(bounds), None)
|
||||
} else {
|
||||
|
@ -819,7 +801,14 @@ impl Workspace {
|
|||
// Use the serialized workspace to construct the new window
|
||||
cx.add_window(
|
||||
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|
||||
|cx| build_workspace(cx),
|
||||
|cx| {
|
||||
Workspace::new(
|
||||
workspace_id,
|
||||
project_handle.clone(),
|
||||
app_state.clone(),
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
.1
|
||||
});
|
||||
|
@ -3120,6 +3109,17 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
|
||||
ZED_WINDOW_POSITION
|
||||
.zip(*ZED_WINDOW_SIZE)
|
||||
.map(|(position, size)| {
|
||||
WindowBounds::Fixed(RectF::new(
|
||||
cx.platform().screens()[0].bounds().origin() + position,
|
||||
size,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
async fn open_items(
|
||||
serialized_workspace: Option<SerializedWorkspace>,
|
||||
workspace: &WeakViewHandle<Workspace>,
|
||||
|
@ -3652,8 +3652,13 @@ pub fn join_remote_project(
|
|||
})
|
||||
.await?;
|
||||
|
||||
let window_bounds_override = window_bounds_env_override(&cx);
|
||||
let (_, workspace) = cx.add_window(
|
||||
(app_state.build_window_options)(None, None, cx.platform().as_ref()),
|
||||
(app_state.build_window_options)(
|
||||
window_bounds_override,
|
||||
None,
|
||||
cx.platform().as_ref(),
|
||||
),
|
||||
|cx| Workspace::new(0, project, app_state.clone(), cx),
|
||||
);
|
||||
(app_state.initialize_workspace)(
|
||||
|
@ -4434,7 +4439,7 @@ mod tests {
|
|||
assert!(!panel.has_focus(cx));
|
||||
});
|
||||
|
||||
// Transfering focus back to the panel keeps it zoomed
|
||||
// Transferring focus back to the panel keeps it zoomed
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_panel_focus::<TestPanel>(cx);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
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_PREVIEW_CHANNEL") {
|
||||
println!("cargo:rustc-env=ZED_PREVIEW_CHANNEL={value}");
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ impl LspAdapter for EsLintLspAdapter {
|
|||
http: Arc<dyn HttpClient>,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
|
||||
// special custom LSP protocol extensions be handled to fully initalize. Download the latest
|
||||
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
|
||||
// prerelease instead to sidestep this issue
|
||||
let release = latest_github_release("microsoft/vscode-eslint", true, http).await?;
|
||||
Ok(Box::new(GitHubLspBinaryVersion {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Allow binary to be called Zed for a nice application menu when running executable direcly
|
||||
// Allow binary to be called Zed for a nice application menu when running executable directly
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
@ -32,6 +32,7 @@ use std::{
|
|||
ffi::OsStr,
|
||||
fs::OpenOptions,
|
||||
io::Write as _,
|
||||
ops::Not,
|
||||
os::unix::prelude::OsStrExt,
|
||||
panic,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -55,9 +56,7 @@ use fs::RealFs;
|
|||
#[cfg(debug_assertions)]
|
||||
use staff_mode::StaffMode;
|
||||
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings, Workspace,
|
||||
};
|
||||
use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
|
||||
use zed::{
|
||||
self, build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
|
||||
};
|
||||
|
@ -70,10 +69,7 @@ fn main() {
|
|||
log::info!("========== starting zed ==========");
|
||||
let mut app = gpui::App::new(Assets).unwrap();
|
||||
|
||||
let app_version = ZED_APP_VERSION
|
||||
.or_else(|| app.platform().app_version().ok())
|
||||
.map_or("dev".to_string(), |v| v.to_string());
|
||||
init_panic_hook(app_version);
|
||||
init_panic_hook(&app);
|
||||
|
||||
app.background();
|
||||
|
||||
|
@ -173,11 +169,6 @@ fn main() {
|
|||
.detach();
|
||||
|
||||
client.telemetry().start();
|
||||
client.telemetry().report_mixpanel_event(
|
||||
"start app",
|
||||
Default::default(),
|
||||
*settings::get::<TelemetrySettings>(cx),
|
||||
);
|
||||
|
||||
let app_state = Arc::new(AppState {
|
||||
languages,
|
||||
|
@ -374,33 +365,96 @@ struct Panic {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
location_data: Option<LocationData>,
|
||||
backtrace: Vec<String>,
|
||||
// TODO
|
||||
// stripped_backtrace: String,
|
||||
time: u128,
|
||||
app_version: String,
|
||||
release_channel: String,
|
||||
os_name: String,
|
||||
os_version: Option<String>,
|
||||
architecture: String,
|
||||
panicked_on: u128,
|
||||
identifying_backtrace: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PanicRequest {
|
||||
panic: Panic,
|
||||
version: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
fn init_panic_hook(app_version: String) {
|
||||
fn init_panic_hook(app: &App) {
|
||||
let is_pty = stdout_is_a_pty();
|
||||
let platform = app.platform();
|
||||
|
||||
panic::set_hook(Box::new(move |info| {
|
||||
let backtrace = Backtrace::new();
|
||||
let app_version = ZED_APP_VERSION
|
||||
.or_else(|| platform.app_version().ok())
|
||||
.map_or("dev".to_string(), |v| v.to_string());
|
||||
|
||||
let thread = thread::current();
|
||||
let thread = thread.name().unwrap_or("<unnamed>");
|
||||
|
||||
let payload = match info.payload().downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match info.payload().downcast_ref::<String>() {
|
||||
Some(s) => &**s,
|
||||
None => "Box<Any>",
|
||||
},
|
||||
};
|
||||
let payload = info.payload();
|
||||
let payload = None
|
||||
.or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
|
||||
.or_else(|| payload.downcast_ref::<String>().map(|s| s.clone()))
|
||||
.unwrap_or_else(|| "Box<Any>".to_string());
|
||||
|
||||
let backtrace = Backtrace::new();
|
||||
let backtrace = backtrace
|
||||
.frames()
|
||||
.iter()
|
||||
.filter_map(|frame| {
|
||||
let symbol = frame.symbols().first()?;
|
||||
let path = symbol.filename()?;
|
||||
Some((path, symbol.lineno(), format!("{:#}", symbol.name()?)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let this_file_path = Path::new(file!());
|
||||
|
||||
// Find the first frame in the backtrace for this panic hook itself. Exclude
|
||||
// that frame and all frames before it.
|
||||
let mut start_frame_ix = 0;
|
||||
let mut codebase_root_path = None;
|
||||
for (ix, (path, _, _)) in backtrace.iter().enumerate() {
|
||||
if path.ends_with(this_file_path) {
|
||||
start_frame_ix = ix + 1;
|
||||
codebase_root_path = path.ancestors().nth(this_file_path.components().count());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude any subsequent frames inside of rust's panic handling system.
|
||||
while let Some((path, _, _)) = backtrace.get(start_frame_ix) {
|
||||
if path.starts_with("/rustc") {
|
||||
start_frame_ix += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build two backtraces:
|
||||
// * one for display, which includes symbol names for all frames, and files
|
||||
// and line numbers for symbols in this codebase
|
||||
// * one for identification and de-duplication, which only includes symbol
|
||||
// names for symbols in this codebase.
|
||||
let mut display_backtrace = Vec::new();
|
||||
let mut identifying_backtrace = Vec::new();
|
||||
for (path, line, symbol) in &backtrace[start_frame_ix..] {
|
||||
display_backtrace.push(symbol.clone());
|
||||
|
||||
if let Some(codebase_root_path) = &codebase_root_path {
|
||||
if let Ok(suffix) = path.strip_prefix(&codebase_root_path) {
|
||||
identifying_backtrace.push(symbol.clone());
|
||||
|
||||
let display_path = suffix.to_string_lossy();
|
||||
if let Some(line) = line {
|
||||
display_backtrace.push(format!(" {display_path}:{line}"));
|
||||
} else {
|
||||
display_backtrace.push(format!(" {display_path}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let panic_data = Panic {
|
||||
thread: thread.into(),
|
||||
|
@ -409,15 +463,23 @@ fn init_panic_hook(app_version: String) {
|
|||
file: location.file().into(),
|
||||
line: location.line(),
|
||||
}),
|
||||
backtrace: format!("{:?}", backtrace)
|
||||
.split("\n")
|
||||
.map(|line| line.to_string())
|
||||
.collect(),
|
||||
// modified_backtrace: None,
|
||||
time: SystemTime::now()
|
||||
app_version: app_version.clone(),
|
||||
release_channel: RELEASE_CHANNEL.dev_name().into(),
|
||||
os_name: platform.os_name().into(),
|
||||
os_version: platform
|
||||
.os_version()
|
||||
.ok()
|
||||
.map(|os_version| os_version.to_string()),
|
||||
architecture: env::consts::ARCH.into(),
|
||||
panicked_on: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
backtrace: display_backtrace,
|
||||
identifying_backtrace: identifying_backtrace
|
||||
.is_empty()
|
||||
.not()
|
||||
.then_some(identifying_backtrace),
|
||||
};
|
||||
|
||||
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
|
||||
|
@ -427,8 +489,7 @@ fn init_panic_hook(app_version: String) {
|
|||
}
|
||||
|
||||
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
|
||||
let panic_file_path =
|
||||
paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, timestamp));
|
||||
let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
|
||||
let panic_file = std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
|
@ -463,15 +524,9 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
|||
continue;
|
||||
};
|
||||
|
||||
let mut components = filename.split('-');
|
||||
if components.next() != Some("zed") {
|
||||
if !filename.starts_with("zed") {
|
||||
continue;
|
||||
}
|
||||
let version = if let Some(version) = components.next() {
|
||||
version
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if telemetry_settings.diagnostics {
|
||||
let panic_data_text = smol::fs::read_to_string(&child_path)
|
||||
|
@ -480,7 +535,6 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
|||
|
||||
let body = serde_json::to_string(&PanicRequest {
|
||||
panic: serde_json::from_str(&panic_data_text)?,
|
||||
version: version.to_string(),
|
||||
token: ZED_SECRET_CLIENT_TOKEN.into(),
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -821,6 +875,6 @@ pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
|
|||
("Go to file", &file_finder::Toggle),
|
||||
("Open command palette", &command_palette::Toggle),
|
||||
("Open recent projects", &recent_projects::OpenRecent),
|
||||
("Change your settings", &OpenSettings),
|
||||
("Change your settings", &zed::OpenSettings),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,10 +12,11 @@ pub fn menus() -> Vec<Menu<'static>> {
|
|||
MenuItem::submenu(Menu {
|
||||
name: "Preferences",
|
||||
items: vec![
|
||||
MenuItem::action("Open Settings", workspace::OpenSettings),
|
||||
MenuItem::action("Open Settings", super::OpenSettings),
|
||||
MenuItem::action("Open Key Bindings", super::OpenKeymap),
|
||||
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
||||
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
||||
MenuItem::action("Open Local Settings", super::OpenLocalSettings),
|
||||
MenuItem::action("Select Theme", theme_selector::Toggle),
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -31,16 +31,23 @@ use project_panel::ProjectPanel;
|
|||
use search::{BufferSearchBar, ProjectSearchBar};
|
||||
use serde::Deserialize;
|
||||
use serde_json::to_string_pretty;
|
||||
use settings::{KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH};
|
||||
use settings::{
|
||||
initial_local_settings_content, KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH,
|
||||
};
|
||||
use std::{borrow::Cow, str, sync::Arc};
|
||||
use terminal_view::terminal_panel::{self, TerminalPanel};
|
||||
use util::{channel::ReleaseChannel, paths, ResultExt};
|
||||
use util::{
|
||||
channel::ReleaseChannel,
|
||||
paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
|
||||
ResultExt,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use welcome::BaseKeymap;
|
||||
pub use workspace;
|
||||
use workspace::{
|
||||
create_and_open_local_file, dock::PanelHandle, open_new, AppState, NewFile, NewWindow,
|
||||
Workspace, WorkspaceSettings,
|
||||
create_and_open_local_file, dock::PanelHandle,
|
||||
notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
|
||||
NewWindow, Workspace, WorkspaceSettings,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Clone, PartialEq)]
|
||||
|
@ -66,6 +73,8 @@ actions!(
|
|||
OpenLicenses,
|
||||
OpenTelemetryLog,
|
||||
OpenKeymap,
|
||||
OpenSettings,
|
||||
OpenLocalSettings,
|
||||
OpenDefaultSettings,
|
||||
OpenDefaultKeymap,
|
||||
IncreaseBufferFontSize,
|
||||
|
@ -158,6 +167,17 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
|
|||
create_and_open_local_file(&paths::KEYMAP, cx, Default::default).detach_and_log_err(cx);
|
||||
},
|
||||
);
|
||||
cx.add_action(
|
||||
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||
settings::initial_user_settings_content(&Assets)
|
||||
.as_ref()
|
||||
.into()
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
},
|
||||
);
|
||||
cx.add_action(open_local_settings_file);
|
||||
cx.add_action(
|
||||
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
|
||||
open_bundled_file(
|
||||
|
@ -555,6 +575,76 @@ pub fn handle_keymap_file_changes(
|
|||
.detach();
|
||||
}
|
||||
|
||||
fn open_local_settings_file(
|
||||
workspace: &mut Workspace,
|
||||
_: &OpenLocalSettings,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
let worktree = project
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
.find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
|
||||
if let Some(worktree) = worktree {
|
||||
let tree_id = worktree.read(cx).id();
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
|
||||
|
||||
if let Some(dir_path) = file_path.parent() {
|
||||
if worktree.read_with(&cx, |tree, _| tree.entry_for_path(dir_path).is_none()) {
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_entry((tree_id, dir_path), true, cx)
|
||||
})
|
||||
.ok_or_else(|| anyhow!("worktree was removed"))?
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if worktree.read_with(&cx, |tree, _| tree.entry_for_path(file_path).is_none()) {
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_entry((tree_id, file_path), false, cx)
|
||||
})
|
||||
.ok_or_else(|| anyhow!("worktree was removed"))?
|
||||
.await?;
|
||||
}
|
||||
|
||||
let editor = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_path((tree_id, file_path), None, true, cx)
|
||||
})?
|
||||
.await?
|
||||
.downcast::<Editor>()
|
||||
.ok_or_else(|| anyhow!("unexpected item type"))?;
|
||||
|
||||
editor
|
||||
.downgrade()
|
||||
.update(&mut cx, |editor, cx| {
|
||||
if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
|
||||
if buffer.read(cx).is_empty() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
[(0..0, initial_local_settings_content(&Assets))],
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
workspace.show_notification(0, cx, |cx| {
|
||||
cx.add_view(|_| MessageNotification::new("This project has no folders open."))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.with_local_workspace(cx, move |workspace, cx| {
|
||||
let app_state = workspace.app_state().clone();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue