Merge remote-tracking branch 'origin/main' into assistant-2

This commit is contained in:
Antonio Scandurra 2023-06-06 19:18:08 +02:00
commit 7a78e64831
68 changed files with 781 additions and 597 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(), "")])

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,4 +18,4 @@ metal = "0.21.0"
objc = "0.2"
[build-dependencies]
bindgen = "0.59.2"
bindgen = "0.65.1"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -756,7 +756,7 @@ mod test {
ˇ
The quick"})
.await;
// Indoc disallows trailing whitspace.
// Indoc disallows trailing whitespace.
cx.assert(" ˇ \nThe quick").await;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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),
]
}

View file

@ -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),
],
}),

View file

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