Compare commits

..

5 Commits

Author SHA1 Message Date
Michael Sloan
5d11fc5bc4 Update edit predictions documentation 2025-02-13 04:47:19 -07:00
Conrad Irwin
d57f5937d4 Git panel: Right click menu (#24787)
Release Notes:

- N/A
2025-02-12 22:26:34 -07:00
Joseph T. Lyons
fc7bf7bcb9 Bump Zed to v0.175 (#24785)
Release Notes:

-N/A
2025-02-12 23:14:45 -05:00
tidely
5d634245a2 remote_server: Remove unnecessary Box, prevent time-of-check time-of-use bug (#24730)
The MultiWrite struct is defined in the function scope and is allowed to
have a concrete type, which means we can throw away the extra Box.
PathBuf::exists is known to be prone to invalid usage. It doesn't take
into account permissions errors and just returns false, additionally it
introduces a time-of-check time-of-use bug. While extremely unlikely,
why not fix it anyway.

Release Notes:

- remove unnecessary Box
- prevent time-of-check time-of-use bug
2025-02-12 20:55:22 -07:00
Conrad Irwin
21a1541a70 Branch/co-authors in commit (#24768)
- **branch selector in commit box**
- **TEMP**
- **Add co-authors toggle button**

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-02-12 20:53:52 -07:00
48 changed files with 800 additions and 1063 deletions

View File

@@ -109,16 +109,8 @@ jobs:
- name: cargo clippy
run: ./script/clippy
- name: Install cargo-machete
uses: clechasseur/rs-cargo@v2
with:
command: install
args: cargo-machete@0.7.0
- name: Check unused dependencies
uses: clechasseur/rs-cargo@v2
with:
command: machete
uses: bnjbvr/cargo-machete@main
- name: Check licenses
run: |
@@ -306,9 +298,8 @@ jobs:
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
@@ -491,6 +482,6 @@ jobs:
- bundle
steps:
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=true
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -62,9 +62,8 @@ jobs:
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}

5
Cargo.lock generated
View File

@@ -4062,6 +4062,7 @@ dependencies = [
"http_client",
"indoc",
"inline_completion",
"inventory",
"itertools 0.14.0",
"language",
"linkify",
@@ -5357,6 +5358,7 @@ dependencies = [
"fuzzy",
"git",
"gpui",
"itertools 0.14.0",
"language",
"menu",
"multi_buffer",
@@ -5371,7 +5373,6 @@ dependencies = [
"settings",
"theme",
"time",
"time_format",
"ui",
"util",
"windows 0.58.0",
@@ -16625,7 +16626,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.174.8"
version = "0.175.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -24,10 +24,10 @@
"shift-escape": "workspace::ToggleZoom",
"open": "workspace::Open",
"ctrl-o": "workspace::Open",
"ctrl-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-=": "zed::IncreaseBufferFontSize",
"ctrl-+": "zed::IncreaseBufferFontSize",
"ctrl--": "zed::DecreaseBufferFontSize",
"ctrl-0": "zed::ResetBufferFontSize",
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f11": "zed::ToggleFullScreen",
@@ -510,14 +510,13 @@
"context": "Editor && edit_prediction",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction"
"alt-l": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && edit_prediction_conflict",
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction"
}
},

View File

@@ -28,10 +28,10 @@
"cmd-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"cmd-o": "workspace::Open",
"cmd-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
"cmd-=": "zed::IncreaseBufferFontSize",
"cmd-+": "zed::IncreaseBufferFontSize",
"cmd--": "zed::DecreaseBufferFontSize",
"cmd-0": "zed::ResetBufferFontSize",
"cmd-,": "zed::OpenSettings",
"cmd-q": "zed::Quit",
"cmd-h": "zed::Hide",
@@ -583,15 +583,14 @@
{
"context": "Editor && edit_prediction",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction"
"alt-tab": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && edit_prediction_conflict",
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::AcceptEditPrediction"
"tab": "editor::AcceptEditPrediction"
}
},
{

View File

@@ -9,8 +9,8 @@
{
"context": "Editor",
"bindings": {
"ctrl->": ["zed::IncreaseBufferFontSize", { "persist": true }],
"ctrl-<": ["zed::DecreaseBufferFontSize", { "persist": true }],
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-d": "editor::DuplicateSelection",
"ctrl-y": "editor::DeleteLine",

View File

@@ -8,8 +8,8 @@
{
"context": "Editor",
"bindings": {
"ctrl->": ["zed::IncreaseBufferFontSize", { "persist": true }],
"ctrl-<": ["zed::DecreaseBufferFontSize", { "persist": true }],
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"cmd-d": "editor::DuplicateSelection",
"cmd-backspace": "editor::DeleteLine",

View File

@@ -696,7 +696,7 @@
}
},
{
"context": "Editor && edit_prediction",
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
// This is identical to the binding in the base keymap, but the vim bindings above to
// "vim::Tab" shadow it, so it needs to be bound again.
@@ -704,7 +704,7 @@
}
},
{
"context": "os != macos && Editor && edit_prediction_conflict",
"context": "os != macos && Editor && edit_prediction",
"bindings": {
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
// is because alt-tab may not be available, as it is often used for window switching on Linux

View File

@@ -30,8 +30,6 @@ pub enum Model {
#[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
Claude3_7Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
@@ -61,8 +59,6 @@ impl Model {
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-5-sonnet") {
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-7-sonnet") {
Ok(Self::Claude3_7Sonnet)
} else if id.starts_with("claude-3-5-haiku") {
Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-opus") {
@@ -79,7 +75,6 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
@@ -90,7 +85,6 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3Opus => "Claude 3 Opus",
@@ -104,14 +98,13 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
}),
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
})
}
Self::Custom {
cache_configuration,
..
@@ -124,7 +117,6 @@ impl Model {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3_7Sonnet
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200_000,
@@ -135,7 +127,7 @@ impl Model {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
Self::Claude3_5Sonnet | Self::Claude3_7Sonnet | Self::Claude3_5Haiku => 8_192,
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
@@ -145,7 +137,6 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
| Self::Claude3_5Haiku
| Self::Claude3Opus
| Self::Claude3Sonnet

View File

@@ -13,7 +13,7 @@ use rope::Point;
use settings::Settings;
use std::time::Duration;
use text::Bias;
use theme::{get_ui_font_size, ThemeSettings};
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
};
@@ -369,7 +369,11 @@ impl Render for MessageEditor {
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: (-get_ui_font_size(cx) * 2) - px(4.0),
y: px(-ThemeSettings::clamp_font_size(
ThemeSettings::get_global(cx).ui_font_size,
)
.0 * 2.0)
- px(4.0),
})
.with_handle(self.inline_context_picker_menu_handle.clone()),
)

View File

@@ -256,7 +256,6 @@ async fn perform_completion(
// so that users can use the new version, without having to update Zed.
request.model = match model.as_str() {
"claude-3-5-sonnet" => anthropic::Model::Claude3_5Sonnet.id().to_string(),
"claude-3-7-sonnet" => anthropic::Model::Claude3_7Sonnet.id().to_string(),
"claude-3-opus" => anthropic::Model::Claude3Opus.id().to_string(),
"claude-3-haiku" => anthropic::Model::Claude3Haiku.id().to_string(),
"claude-3-sonnet" => anthropic::Model::Claude3Sonnet.id().to_string(),

View File

@@ -40,21 +40,13 @@ pub enum Model {
O3Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,
#[serde(alias = "claude-3-7-sonnet", rename = "claude-3.7-sonnet")]
Claude3_7Sonnet,
#[serde(alias = "gemini-2.0-flash", rename = "gemini-2.0-flash-001")]
Gemini20Flash,
}
impl Model {
pub fn uses_streaming(&self) -> bool {
match self {
Self::Gpt4o
| Self::Gpt4
| Self::Gpt3_5Turbo
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet => true,
Self::O3Mini | Self::O1 | Self::Gemini20Flash => false,
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
Self::O3Mini | Self::O1 => false,
}
}
@@ -66,8 +58,6 @@ impl Model {
"o1" => Ok(Self::O1),
"o3-mini" => Ok(Self::O3Mini),
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
"claude-3-7-sonnet" => Ok(Self::Claude3_7Sonnet),
"gemini-2.0-flash-001" => Ok(Self::Gemini20Flash),
_ => Err(anyhow!("Invalid model id: {}", id)),
}
}
@@ -80,8 +70,6 @@ impl Model {
Self::O3Mini => "o3-mini",
Self::O1 => "o1",
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
Self::Claude3_7Sonnet => "claude-3-7-sonnet",
Self::Gemini20Flash => "gemini-2.0-flash-001",
}
}
@@ -93,21 +81,17 @@ impl Model {
Self::O3Mini => "o3-mini",
Self::O1 => "o1",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
Self::Gemini20Flash => "Gemini 2.0 Flash",
}
}
pub fn max_token_count(&self) -> usize {
match self {
Self::Gpt4o => 64_000,
Self::Gpt4 => 32_768,
Self::Gpt3_5Turbo => 12_288,
Self::O3Mini => 64_000,
Self::O1 => 20_000,
Self::Gpt4o => 64000,
Self::Gpt4 => 32768,
Self::Gpt3_5Turbo => 12288,
Self::O3Mini => 20000,
Self::O1 => 20000,
Self::Claude3_5Sonnet => 200_000,
Self::Claude3_7Sonnet => 90_000,
Model::Gemini20Flash => 128_000,
}
}
}

View File

@@ -29,8 +29,14 @@ impl Template for KeybindingTemplate {
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
let action = args.get("action").map(String::as_str).unwrap_or("");
let macos_binding = context.find_binding("macos", action).unwrap_or_default();
let linux_binding = context.find_binding("linux", action).unwrap_or_default();
let macos_binding = context
.find_binding("macos", action)
.unwrap_or_default()
.replace("\\", "&#92;");
let linux_binding = context
.find_binding("linux", action)
.unwrap_or_default()
.replace("\\", "&#92;");
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
}
}

View File

@@ -49,6 +49,7 @@ gpui.workspace = true
http_client.workspace = true
indoc.workspace = true
inline_completion.workspace = true
inventory.workspace = true
itertools.workspace = true
language.workspace = true
linkify.workspace = true

View File

@@ -67,7 +67,10 @@ use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
};
use futures::{future, FutureExt};
use futures::{
future::{self, Shared},
FutureExt,
};
use fuzzy::StringMatchCandidate;
use code_context_menus::{
@@ -159,15 +162,12 @@ use std::{
pub use sum_tree::Bias;
use sum_tree::TreeMap;
use text::{BufferId, OffsetUtf16, Rope};
use theme::{
observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
ThemeColors, ThemeSettings,
};
use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings};
use ui::{
h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconName, IconSize, Key,
Tooltip,
};
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TakeUntilExt, TryFutureExt};
use workspace::item::{ItemHandle, PreviewTabsSettings};
use workspace::notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt};
use workspace::{
@@ -194,7 +194,8 @@ pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
pub(crate) const EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT: &str =
"edit_prediction_requires_modifier";
pub fn render_parsed_markdown(
element_id: impl Into<ElementId>,
@@ -507,6 +508,15 @@ enum EditPredictionSettings {
},
}
impl EditPredictionSettings {
pub fn is_enabled(&self) -> bool {
match self {
EditPredictionSettings::Disabled => false,
EditPredictionSettings::Enabled { .. } => true,
}
}
}
enum InlineCompletionHighlight {}
pub enum MenuInlineCompletionsPolicy {
@@ -754,6 +764,7 @@ pub struct Editor {
next_scroll_position: NextScrollCursorCenterTopBottom,
addons: HashMap<TypeId, Box<dyn Addon>>,
registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
load_diff_task: Option<Shared<Task<()>>>,
selection_mark_mode: bool,
toggle_fold_multiple_buffers: Task<()>,
_scroll_cursor_center_top_bottom_task: Task<()>,
@@ -1311,12 +1322,16 @@ impl Editor {
};
let mut code_action_providers = Vec::new();
let mut load_uncommitted_diff = None;
if let Some(project) = project.clone() {
get_uncommitted_diff_for_buffer(
&project,
buffer.read(cx).all_buffers(),
buffer.clone(),
cx,
load_uncommitted_diff = Some(
get_uncommitted_diff_for_buffer(
&project,
buffer.read(cx).all_buffers(),
buffer.clone(),
cx,
)
.shared(),
);
code_action_providers.push(Rc::new(project) as Rc<_>);
}
@@ -1440,7 +1455,6 @@ impl Editor {
cx.observe_in(&display_map, window, Self::on_display_map_changed),
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
cx.observe_window_activation(window, |editor, window, cx| {
let active = window.is_window_active();
editor.blink_manager.update(cx, |blink_manager, cx| {
@@ -1465,6 +1479,7 @@ impl Editor {
selection_mark_mode: false,
toggle_fold_multiple_buffers: Task::ready(()),
text_style_refinement: None,
load_diff_task: load_uncommitted_diff,
};
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
this._subscriptions.extend(project_subscriptions);
@@ -1530,10 +1545,13 @@ impl Editor {
key_context.add("renaming");
}
let mut showing_completions = false;
match self.context_menu.borrow().as_ref() {
Some(CodeContextMenu::Completions(_)) => {
key_context.add("menu");
key_context.add("showing_completions");
showing_completions = true;
}
Some(CodeContextMenu::CodeActions(_)) => {
key_context.add("menu");
@@ -1561,11 +1579,15 @@ impl Editor {
}
if has_active_edit_prediction {
if self.edit_prediction_in_conflict() {
key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
} else {
key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
key_context.add("copilot_suggestion");
key_context.add("copilot_suggestion");
key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
if showing_completions
|| self.edit_prediction_requires_modifier()
// Require modifier key when the cursor is on leading whitespace, to allow `tab`
// bindings to insert tab characters.
|| (self.edit_prediction_requires_modifier_in_leading_space && self.edit_prediction_cursor_on_leading_whitespace)
{
key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT);
}
}
@@ -1576,52 +1598,18 @@ impl Editor {
key_context
}
pub fn edit_prediction_in_conflict(&self) -> bool {
if !self.show_edit_predictions_in_menu() {
return false;
}
let showing_completions = self
.context_menu
.borrow()
.as_ref()
.map_or(false, |context| {
matches!(context, CodeContextMenu::Completions(_))
});
showing_completions
|| self.edit_prediction_requires_modifier()
// Require modifier key when the cursor is on leading whitespace, to allow `tab`
// bindings to insert tab characters.
|| (self.edit_prediction_requires_modifier_in_leading_space && self.edit_prediction_cursor_on_leading_whitespace)
}
pub fn accept_edit_prediction_keybind(
&self,
window: &Window,
cx: &App,
) -> AcceptEditPredictionBinding {
let key_context = self.key_context_internal(true, window, cx);
let in_conflict = self.edit_prediction_in_conflict();
AcceptEditPredictionBinding(
window
.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
.into_iter()
.filter(|binding| {
!in_conflict
|| binding
.keystrokes()
.first()
.map_or(false, |keystroke| keystroke.modifiers.modified())
})
.rev()
.min_by_key(|binding| {
binding
.keystrokes()
.first()
.map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
}),
.next(),
)
}
@@ -4986,7 +4974,6 @@ impl Editor {
.contains(&target.to_display_point(&position_map.snapshot).row())
|| !self.edit_prediction_requires_modifier()
{
self.unfold_ranges(&[target..target], true, false, cx);
// Note that this is also done in vim's handler of the Tab action.
self.change_selections(
Some(Autoscroll::newest()),
@@ -5310,6 +5297,11 @@ impl Editor {
self.edit_prediction_settings =
self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
if !self.edit_prediction_settings.is_enabled() {
self.discard_inline_completion(false, cx);
return None;
}
self.edit_prediction_cursor_on_leading_whitespace =
multibuffer.is_line_whitespace_upto(cursor);
@@ -5989,23 +5981,52 @@ impl Editor {
} => {
let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
let highlighted_edits = crate::inline_completion_edit_text(
&snapshot,
&edits,
edit_preview.as_ref()?,
true,
cx,
)
.first_line_preview();
);
let styled_text = gpui::StyledText::new(highlighted_edits.text)
.with_highlights(&style.text, highlighted_edits.highlights);
let len_total = highlighted_edits.text.len();
let first_line = &highlighted_edits.text
[..highlighted_edits.text.find('\n').unwrap_or(len_total)];
let first_line_len = first_line.len();
let first_highlight_start = highlighted_edits
.highlights
.first()
.map_or(0, |(range, _)| range.start);
let drop_prefix_len = first_line
.char_indices()
.find(|(_, c)| !c.is_whitespace())
.map_or(first_highlight_start, |(ix, _)| {
ix.min(first_highlight_start)
});
let preview_text = &first_line[drop_prefix_len..];
let preview_len = preview_text.len();
let highlights = highlighted_edits
.highlights
.into_iter()
.take_until(|(range, _)| range.start > first_line_len)
.map(|(range, style)| {
(
range.start - drop_prefix_len
..(range.end - drop_prefix_len).min(preview_len),
style,
)
});
let styled_text = gpui::StyledText::new(SharedString::new(preview_text))
.with_highlights(&style.text, highlights);
let preview = h_flex()
.gap_1()
.min_w_16()
.child(styled_text)
.when(has_more_lines, |parent| parent.child(""));
.when(len_total > first_line_len, |parent| parent.child(""));
let left = if first_edit_row != cursor_point.row {
render_relative_row_jump("", cursor_point.row, first_edit_row)
@@ -14108,11 +14129,14 @@ impl Editor {
let buffer_id = buffer.read(cx).remote_id();
if self.buffer.read(cx).diff_for(buffer_id).is_none() {
if let Some(project) = &self.project {
get_uncommitted_diff_for_buffer(
project,
[buffer.clone()],
self.buffer.clone(),
cx,
self.load_diff_task = Some(
get_uncommitted_diff_for_buffer(
project,
[buffer.clone()],
self.buffer.clone(),
cx,
)
.shared(),
);
}
}
@@ -14867,6 +14891,10 @@ impl Editor {
gpui::Size::new(em_width, line_height)
}
pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
self.load_diff_task.clone()
}
}
fn get_uncommitted_diff_for_buffer(
@@ -14874,7 +14902,7 @@ fn get_uncommitted_diff_for_buffer(
buffers: impl IntoIterator<Item = Entity<Buffer>>,
buffer: Entity<MultiBuffer>,
cx: &mut App,
) {
) -> Task<()> {
let mut tasks = Vec::new();
project.update(cx, |project, cx| {
for buffer in buffers {
@@ -14891,7 +14919,6 @@ fn get_uncommitted_diff_for_buffer(
})
.ok();
})
.detach();
}
fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
@@ -16090,7 +16117,7 @@ impl Render for Editor {
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_size: settings.buffer_font_size().into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()

View File

@@ -15,13 +15,14 @@ use crate::{
items::BufferSearchHighlights,
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus};
@@ -34,11 +35,11 @@ use gpui::{
point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App,
AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners,
CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement,
WeakEntity, Window,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement,
KeyBindingContextPredicate, Keystroke, Length, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
Subscription, TextRun, TextStyleRefinement, WeakEntity, Window,
};
use itertools::Itertools;
use language::{
@@ -54,7 +55,7 @@ use multi_buffer::{
RowInfo, ToOffset,
};
use project::project_settings::{GitGutterSetting, ProjectSettings};
use settings::Settings;
use settings::{KeyBindingValidator, KeyBindingValidatorRegistration, Settings};
use smallvec::{smallvec, SmallVec};
use std::{
any::TypeId,
@@ -74,7 +75,7 @@ use ui::{
POPOVER_Y_PADDING,
};
use unicode_segmentation::UnicodeSegmentation;
use util::{RangeExt, ResultExt};
use util::{markdown::MarkdownString, RangeExt, ResultExt};
use workspace::{item::Item, notifications::NotifyTaskExt, Workspace};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
@@ -5833,6 +5834,50 @@ impl AcceptEditPredictionBinding {
}
}
struct AcceptEditPredictionsBindingValidator;
inventory::submit! { KeyBindingValidatorRegistration(|| Box::new(AcceptEditPredictionsBindingValidator)) }
impl KeyBindingValidator for AcceptEditPredictionsBindingValidator {
fn action_type_id(&self) -> TypeId {
TypeId::of::<AcceptEditPrediction>()
}
fn validate(&self, binding: &gpui::KeyBinding) -> Result<(), MarkdownString> {
use KeyBindingContextPredicate::*;
if binding.keystrokes().len() == 1 && binding.keystrokes()[0].modifiers.modified() {
return Ok(());
}
let required_predicate =
Not(Identifier(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT.into()).into());
match binding.predicate() {
Some(predicate) if required_predicate.is_superset(&predicate) => {
return Ok(());
}
_ => {}
}
let negated_requires_modifier_key_context = MarkdownString::inline_code(&format!(
"!{}",
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT
));
Err(MarkdownString(format!(
"{} can only be bound to a single keystroke with modifiers, so \
that pressing these modifiers can be used for prediction \
preview.\n\n\
This restriction does not apply when the context requires {}, \
since these bindings are not used for prediction preview. For \
example, in the default keymap `tab` requires {}, and `alt-tab` \
is used otherwise.\n\n\
See [the documentation]({}) for more details.",
MarkdownString::inline_code(AcceptEditPrediction.name()),
negated_requires_modifier_key_context.clone(),
negated_requires_modifier_key_context,
"https://zed.dev/docs/completions#edit-predictions",
)))
}
}
#[allow(clippy::too_many_arguments)]
fn prepaint_gutter_button(
button: IconButton,

View File

@@ -371,7 +371,7 @@ impl GitRepository for RealGitRepository {
"%(contents:subject)",
]
.join("%00");
let args = vec!["for-each-ref", "refs/heads/**/*", "--format", &fields];
let args = vec!["for-each-ref", "refs/heads/*", "--format", &fields];
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)

View File

@@ -14,15 +14,16 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
buffer_diff.workspace = true
collections.workspace = true
db.workspace = true
buffer_diff.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
menu.workspace = true
multi_buffer.workspace = true
@@ -37,7 +38,6 @@ serde_json.workspace = true
settings.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -8,14 +8,15 @@ use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use editor::commit_tooltip::CommitTooltip;
use editor::{
actions::MoveToEnd, scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode,
EditorSettings, MultiBuffer, ShowScrollbar,
scroll::ScrollbarAutoHide, Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer,
ShowScrollbar,
};
use git::repository::{CommitDetails, ResetMode};
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
use gpui::*;
use itertools::Itertools;
use language::{markdown, Buffer, File, ParsedMarkdown};
use menu::{SelectFirst, SelectLast, SelectNext, SelectPrev};
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
use multi_buffer::ExcerptInfo;
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, PanelHeader};
use project::{
@@ -27,10 +28,11 @@ use settings::Settings as _;
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration, usize};
use time::OffsetDateTime;
use ui::{
prelude::*, ButtonLike, Checkbox, CheckboxWithLabel, Divider, DividerColor, ElevationIndex,
IndentGuideColors, ListItem, ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
prelude::*, ButtonLike, Checkbox, ContextMenu, Divider, DividerColor, ElevationIndex, ListItem,
ListItemSpacing, Scrollbar, ScrollbarState, Tooltip,
};
use util::{maybe, ResultExt, TryFutureExt};
use workspace::SaveIntent;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotificationId},
@@ -45,7 +47,7 @@ actions!(
OpenMenu,
FocusEditor,
FocusChanges,
FillCoAuthors,
ToggleFillCoAuthors,
]
);
@@ -78,7 +80,6 @@ pub fn init(cx: &mut App) {
#[derive(Debug, Clone)]
pub enum Event {
Focus,
OpenedEntry { path: ProjectPath },
}
#[derive(Serialize, Deserialize)]
@@ -111,7 +112,7 @@ impl GitHeaderEntry {
pub fn title(&self) -> &'static str {
match self.header {
Section::Conflict => "Conflicts",
Section::Tracked => "Changed",
Section::Tracked => "Changes",
Section::New => "New",
}
}
@@ -154,7 +155,7 @@ pub struct GitPanel {
conflicted_count: usize,
conflicted_staged_count: usize,
current_modifiers: Modifiers,
enable_auto_coauthors: bool,
add_coauthors: bool,
entries: Vec<GitListEntry>,
entries_by_path: collections::HashMap<RepoPath, usize>,
focus_handle: FocusHandle,
@@ -176,6 +177,7 @@ pub struct GitPanel {
update_visible_entries_task: Task<()>,
width: Option<Pixels>,
workspace: WeakEntity<Workspace>,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
}
fn commit_message_editor(
@@ -214,7 +216,7 @@ impl GitPanel {
let active_repository = project.read(cx).active_repository(cx);
let workspace = cx.entity().downgrade();
let git_panel = cx.new(|cx| {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, window, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
@@ -260,7 +262,7 @@ impl GitPanel {
conflicted_count: 0,
conflicted_staged_count: 0,
current_modifiers: window.modifiers(),
enable_auto_coauthors: true,
add_coauthors: true,
entries: Vec::new(),
entries_by_path: HashMap::default(),
focus_handle: cx.focus_handle(),
@@ -281,30 +283,13 @@ impl GitPanel {
tracked_staged_count: 0,
update_visible_entries_task: Task::ready(()),
width: Some(px(360.)),
context_menu: None,
workspace,
};
git_panel.schedule_update(false, window, cx);
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
git_panel
});
cx.subscribe_in(
&git_panel,
window,
move |workspace, _, event: &Event, window, cx| match event.clone() {
Event::OpenedEntry { path } => {
workspace
.open_path_preview(path, None, false, false, window, cx)
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
Some(format!("{e}"))
});
}
Event::Focus => { /* TODO */ }
},
)
.detach();
git_panel
})
}
pub fn select_entry_by_path(
@@ -467,7 +452,7 @@ impl GitPanel {
fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
if self.entries.first().is_some() {
self.selected_entry = Some(0);
self.selected_entry = Some(1);
self.scroll_to_selected_entry(cx);
}
}
@@ -485,7 +470,16 @@ impl GitPanel {
selected_entry
};
self.selected_entry = Some(new_selected_entry);
if matches!(
self.entries.get(new_selected_entry),
Some(GitListEntry::Header(..))
) {
if new_selected_entry > 0 {
self.selected_entry = Some(new_selected_entry - 1)
}
} else {
self.selected_entry = Some(new_selected_entry);
}
self.scroll_to_selected_entry(cx);
}
@@ -505,8 +499,14 @@ impl GitPanel {
} else {
selected_entry
};
self.selected_entry = Some(new_selected_entry);
if matches!(
self.entries.get(new_selected_entry),
Some(GitListEntry::Header(..))
) {
self.selected_entry = Some(new_selected_entry + 1);
} else {
self.selected_entry = Some(new_selected_entry);
}
self.scroll_to_selected_entry(cx);
}
@@ -536,7 +536,7 @@ impl GitPanel {
active_repository.read(cx).entry_count() > 0
});
if have_entries && self.selected_entry.is_none() {
self.selected_entry = Some(0);
self.selected_entry = Some(1);
self.scroll_to_selected_entry(cx);
cx.notify();
}
@@ -558,7 +558,7 @@ impl GitPanel {
self.selected_entry.and_then(|i| self.entries.get(i))
}
fn open_selected(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
maybe!({
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
@@ -571,6 +571,121 @@ impl GitPanel {
self.focus_handle.focus(window);
}
fn open_file(
&mut self,
_: &menu::SecondaryConfirm,
window: &mut Window,
cx: &mut Context<Self>,
) {
maybe!({
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
let active_repo = self.active_repository.as_ref()?;
let path = active_repo
.read(cx)
.repo_path_to_project_path(&entry.repo_path)?;
if entry.status.is_deleted() {
return None;
}
self.workspace
.update(cx, |workspace, cx| {
workspace
.open_path_preview(path, None, false, false, window, cx)
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
Some(format!("{e}"))
});
})
.ok()
});
}
fn revert(
&mut self,
_: &editor::actions::RevertFile,
window: &mut Window,
cx: &mut Context<Self>,
) {
maybe!({
let list_entry = self.entries.get(self.selected_entry?)?.clone();
let entry = list_entry.status_entry()?;
let active_repo = self.active_repository.as_ref()?;
let path = active_repo
.read(cx)
.repo_path_to_project_path(&entry.repo_path)?;
let workspace = self.workspace.clone();
if entry.status.is_staged() != Some(false) {
self.update_staging_area_for_entries(false, vec![entry.repo_path.clone()], cx);
}
if entry.status.is_created() {
let prompt = window.prompt(
PromptLevel::Info,
"Do you want to trash this file?",
None,
&["Trash", "Cancel"],
cx,
);
cx.spawn_in(window, |_, mut cx| async move {
match prompt.await {
Ok(0) => {}
_ => return Ok(()),
}
let task = workspace.update(&mut cx, |workspace, cx| {
workspace
.project()
.update(cx, |project, cx| project.delete_file(path, true, cx))
})?;
if let Some(task) = task {
task.await?;
}
Ok(())
})
.detach_and_prompt_err(
"Failed to trash file",
window,
cx,
|e, _, _| Some(format!("{e}")),
);
return Some(());
}
let open_path = workspace.update(cx, |workspace, cx| {
workspace.open_path_preview(path, None, true, false, window, cx)
});
cx.spawn_in(window, |_, mut cx| async move {
let item = open_path?.await?;
let editor = cx.update(|_, cx| {
item.act_as::<Editor>(cx)
.ok_or_else(|| anyhow::anyhow!("didn't open editor"))
})??;
if let Some(task) =
editor.update(&mut cx, |editor, _| editor.wait_for_diff_to_load())?
{
task.await
};
editor.update_in(&mut cx, |editor, window, cx| {
editor.revert_file(&Default::default(), window, cx);
})?;
workspace
.update_in(&mut cx, |workspace, window, cx| {
workspace.save_active_item(SaveIntent::Save, window, cx)
})?
.await?;
Ok(())
})
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
Some(format!("{e}"))
});
Some(())
});
}
fn toggle_staged_for_entry(
&mut self,
entry: &GitListEntry,
@@ -605,7 +720,18 @@ impl GitPanel {
(goal_staged_state, entries)
}
};
self.update_staging_area_for_entries(stage, repo_paths, cx);
}
fn update_staging_area_for_entries(
&mut self,
stage: bool,
repo_paths: Vec<RepoPath>,
cx: &mut Context<Self>,
) {
let Some(active_repository) = self.active_repository.clone() else {
return;
};
let op_id = self.pending.iter().map(|p| p.op_id).max().unwrap_or(0) + 1;
self.pending.push(PendingOperation {
op_id,
@@ -614,7 +740,6 @@ impl GitPanel {
finished: false,
});
let repo_paths = repo_paths.clone();
let active_repository = active_repository.clone();
let repository = active_repository.read(cx);
self.update_counts(repository);
cx.notify();
@@ -696,11 +821,14 @@ impl GitPanel {
return;
}
let message = self.commit_editor.read(cx).text(cx);
let mut message = self.commit_editor.read(cx).text(cx);
if message.trim().is_empty() {
self.commit_editor.read(cx).focus_handle(cx).focus(window);
return;
}
if self.add_coauthors {
self.fill_co_authors(&mut message, cx);
}
let task = if self.has_staged_changes() {
// Repository serializes all git operations, so we can just send a commit immediately
@@ -781,19 +909,70 @@ impl GitPanel {
self.pending_commit = Some(task);
}
fn fill_co_authors(&mut self, _: &FillCoAuthors, window: &mut Window, cx: &mut Context<Self>) {
const CO_AUTHOR_PREFIX: &str = "Co-authored-by: ";
fn potential_co_authors(&self, cx: &App) -> Vec<(String, String)> {
let mut new_co_authors = Vec::new();
let project = self.project.read(cx);
let Some(room) = self
.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned())
else {
return;
return Vec::default();
};
let mut existing_text = self.commit_editor.read(cx).text(cx);
existing_text.make_ascii_lowercase();
let room = room.read(cx);
for (peer_id, collaborator) in project.collaborators() {
if collaborator.is_host {
continue;
}
let Some(participant) = room.remote_participant_for_peer_id(*peer_id) else {
continue;
};
if participant.can_write() && participant.user.email.is_some() {
let email = participant.user.email.clone().unwrap();
new_co_authors.push((
participant
.user
.name
.clone()
.unwrap_or_else(|| participant.user.github_login.clone()),
email,
))
}
}
if !project.is_local() && !project.is_read_only(cx) {
if let Some(user) = room.local_participant_user(cx) {
if let Some(email) = user.email.clone() {
new_co_authors.push((
user.name
.clone()
.unwrap_or_else(|| user.github_login.clone()),
email.clone(),
))
}
}
}
new_co_authors
}
fn toggle_fill_co_authors(
&mut self,
_: &ToggleFillCoAuthors,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.add_coauthors = !self.add_coauthors;
cx.notify();
}
fn fill_co_authors(&mut self, message: &mut String, cx: &mut Context<Self>) {
const CO_AUTHOR_PREFIX: &str = "Co-authored-by: ";
let existing_text = message.to_ascii_lowercase();
let lowercase_co_author_prefix = CO_AUTHOR_PREFIX.to_lowercase();
let mut ends_with_co_authors = false;
let existing_co_authors = existing_text
@@ -810,70 +989,32 @@ impl GitPanel {
})
.collect::<HashSet<_>>();
let project = self.project.read(cx);
let room = room.read(cx);
let mut new_co_authors = Vec::new();
let new_co_authors = self
.potential_co_authors(cx)
.into_iter()
.filter(|(_, email)| {
!existing_co_authors
.iter()
.any(|existing| existing.contains(email.as_str()))
})
.collect::<Vec<_>>();
for (peer_id, collaborator) in project.collaborators() {
if collaborator.is_host {
continue;
}
let Some(participant) = room.remote_participant_for_peer_id(*peer_id) else {
continue;
};
if participant.can_write() && participant.user.email.is_some() {
let email = participant.user.email.clone().unwrap();
if !existing_co_authors.contains(&email.as_ref()) {
new_co_authors.push((
participant
.user
.name
.clone()
.unwrap_or_else(|| participant.user.github_login.clone()),
email,
))
}
}
}
if !project.is_local() && !project.is_read_only(cx) {
if let Some(user) = room.local_participant_user(cx) {
if let Some(email) = user.email.clone() {
if !existing_co_authors.contains(&email.as_ref()) {
new_co_authors.push((
user.name
.clone()
.unwrap_or_else(|| user.github_login.clone()),
email.clone(),
))
}
}
}
}
if new_co_authors.is_empty() {
return;
}
self.commit_editor.update(cx, |editor, cx| {
let editor_end = editor.buffer().read(cx).read(cx).len();
let mut edit = String::new();
if !ends_with_co_authors {
edit.push('\n');
}
for (name, email) in new_co_authors {
edit.push('\n');
edit.push_str(CO_AUTHOR_PREFIX);
edit.push_str(&name);
edit.push_str(" <");
edit.push_str(&email);
edit.push('>');
}
editor.edit(Some((editor_end..editor_end, edit)), cx);
editor.move_to_end(&MoveToEnd, window, cx);
editor.focus_handle(cx).focus(window);
});
if !ends_with_co_authors {
message.push('\n');
}
for (name, email) in new_co_authors {
message.push('\n');
message.push_str(CO_AUTHOR_PREFIX);
message.push_str(&name);
message.push_str(" <");
message.push_str(&email);
message.push('>');
}
message.push('\n');
}
fn schedule_update(
@@ -1046,11 +1187,6 @@ impl GitPanel {
cx.notify();
}
fn toggle_auto_coauthors(&mut self, cx: &mut Context<Self>) {
self.enable_auto_coauthors = !self.enable_auto_coauthors;
cx.notify();
}
fn header_state(&self, header_type: Section) -> ToggleState {
let (staged_count, count) = match header_type {
Section::New => (self.new_staged_count, self.new_count),
@@ -1241,14 +1377,59 @@ impl GitPanel {
cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
});
let enable_coauthors = CheckboxWithLabel::new(
"enable-coauthors",
Label::new("Add Co-authors")
.color(Color::Disabled)
.size(LabelSize::XSmall),
self.enable_auto_coauthors.into(),
cx.listener(move |this, _, _, cx| this.toggle_auto_coauthors(cx)),
);
let potential_co_authors = self.potential_co_authors(cx);
let enable_coauthors = if potential_co_authors.is_empty() {
None
} else {
Some(
IconButton::new("co-authors", IconName::Person)
.icon_color(Color::Disabled)
.selected_icon_color(Color::Selected)
.toggle_state(self.add_coauthors)
.tooltip(move |_, cx| {
let title = format!(
"Add co-authored-by:{}{}",
if potential_co_authors.len() == 1 {
""
} else {
"\n"
},
potential_co_authors
.iter()
.map(|(name, email)| format!(" {} <{}>", name, email))
.join("\n")
);
Tooltip::simple(title, cx)
})
.on_click(cx.listener(|this, _, _, cx| {
this.add_coauthors = !this.add_coauthors;
cx.notify();
})),
)
};
let branch = self
.active_repository
.as_ref()
.and_then(|repo| repo.read(cx).branch().map(|b| b.name.clone()))
.unwrap_or_else(|| "<no branch>".into());
let branch_selector = Button::new("branch-selector", branch)
.color(Color::Muted)
.style(ButtonStyle::Subtle)
.icon(IconName::GitBranch)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.icon_position(IconPosition::Start)
.tooltip(Tooltip::for_action_title(
"Switch Branch",
&zed_actions::git::Branch,
))
.on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
}))
.style(ButtonStyle::Transparent);
let footer_size = px(32.);
let gap = px(16.0);
@@ -1274,7 +1455,7 @@ impl GitPanel {
.left_2()
.h(footer_size)
.flex_none()
.child(enable_coauthors),
.child(branch_selector),
)
.child(
h_flex()
@@ -1283,6 +1464,7 @@ impl GitPanel {
.right_2()
.h(footer_size)
.flex_none()
.children(enable_coauthors)
.child(commit_button),
)
}
@@ -1301,32 +1483,6 @@ impl GitPanel {
}) {
return None;
}
let _branch_selector = Button::new("branch-selector", branch.name.clone())
.color(Color::Muted)
.style(ButtonStyle::Subtle)
.icon(IconName::GitBranch)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.icon_position(IconPosition::Start)
.tooltip(Tooltip::for_action_title(
"Switch Branch",
&zed_actions::git::Branch,
))
.on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
}))
.style(ButtonStyle::Transparent);
let _timestamp = Label::new(time_format::format_local_timestamp(
OffsetDateTime::from_unix_timestamp(commit.commit_timestamp).log_err()?,
OffsetDateTime::now_utc(),
time_format::TimestampFormat::Relative,
))
.size(LabelSize::Small)
.color(Color::Muted);
let tooltip = if self.has_staged_changes() {
"git reset HEAD^ --soft"
} else {
@@ -1374,13 +1530,6 @@ impl GitPanel {
.icon_position(IconPosition::Start)
.tooltip(Tooltip::for_action_title(tooltip, &git::Uncommit))
.on_click(cx.listener(|this, _, window, cx| this.uncommit(window, cx))),
// .child(
// panel_filled_button("Push")
// .icon(IconName::ArrowUp)
// .icon_size(IconSize::Small)
// .icon_color(Color::Muted)
// .icon_position(IconPosition::Start), // .disabled(true),
// ),
),
)
}
@@ -1505,7 +1654,7 @@ impl GitPanel {
fn render_entries(
&self,
has_write_access: bool,
window: &Window,
_: &Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let entry_count = self.entries.len();
@@ -1546,61 +1695,6 @@ impl GitPanel {
items
}
})
.with_decoration(
ui::indent_guides(
cx.entity().clone(),
self.indent_size(window, cx),
IndentGuideColors::panel(cx),
|this, range, _windows, _cx| {
this.entries
.iter()
.skip(range.start)
.map(|entry| match entry {
GitListEntry::GitStatusEntry(_) => 1,
GitListEntry::Header(_) => 0,
})
.collect()
},
)
.with_render_fn(
cx.entity().clone(),
move |_, params, _, _| {
let indent_size = params.indent_size;
let left_offset = indent_size - px(3.0);
let item_height = params.item_height;
params
.indent_guides
.into_iter()
.enumerate()
.map(|(_, layout)| {
let offset = if layout.continues_offscreen {
px(0.)
} else {
px(4.0)
};
let bounds = Bounds::new(
point(
px(layout.offset.x as f32) * indent_size + left_offset,
px(layout.offset.y as f32) * item_height + offset,
),
size(
px(1.),
px(layout.length as f32) * item_height
- px(offset.0 * 2.),
),
);
ui::RenderedIndentGuide {
bounds,
layout,
is_active: false,
hitbox: None,
}
})
.collect()
},
),
)
.size_full()
.with_sizing_behavior(ListSizingBehavior::Infer)
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
@@ -1617,59 +1711,22 @@ impl GitPanel {
&self,
ix: usize,
header: &GitHeaderEntry,
has_write_access: bool,
window: &Window,
cx: &Context<Self>,
_: bool,
_: &Window,
_: &Context<Self>,
) -> AnyElement {
let selected = self.selected_entry == Some(ix);
let header_state = if self.has_staged_changes() {
self.header_state(header.header)
} else {
match header.header {
Section::Tracked | Section::Conflict => ToggleState::Selected,
Section::New => ToggleState::Unselected,
}
};
let checkbox = Checkbox::new(("checkbox", ix), header_state)
.disabled(!has_write_access)
.fill()
.placeholder(!self.has_staged_changes())
.elevation(ElevationIndex::Surface)
.on_click({
let header = header.clone();
cx.listener(move |this, _, window, cx| {
this.toggle_staged_for_entry(&GitListEntry::Header(header.clone()), window, cx);
cx.stop_propagation();
})
});
let start_slot = h_flex()
.id(("start-slot", ix))
.gap(DynamicSpacing::Base04.rems(cx))
.child(checkbox)
.tooltip(|window, cx| Tooltip::for_action("Stage File", &ToggleStaged, window, cx))
.on_mouse_down(MouseButton::Left, |_, _, cx| {
// prevent the list item active state triggering when toggling checkbox
cx.stop_propagation();
});
div()
.w_full()
.child(
ListItem::new(ix)
.spacing(ListItemSpacing::Sparse)
.start_slot(start_slot)
.toggle_state(selected)
.focused(selected && self.focus_handle(cx).is_focused(window))
.disabled(!has_write_access)
.on_click({
cx.listener(move |this, _, _, cx| {
this.selected_entry = Some(ix);
cx.notify();
})
})
.child(h_flex().child(self.entry_label(header.title(), Color::Muted))),
.disabled(true)
.child(
Label::new(header.title())
.color(Color::Muted)
.size(LabelSize::Small)
.single_line(),
),
)
.into_any_element()
}
@@ -1685,6 +1742,50 @@ impl GitPanel {
repo.update(cx, |repo, cx| repo.show(sha, cx))
}
fn deploy_context_menu(
&mut self,
position: Point<Pixels>,
ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(entry) = self.entries.get(ix).and_then(|e| e.status_entry()) else {
return;
};
let revert_title = if entry.status.is_deleted() {
"Restore file"
} else if entry.status.is_created() {
"Trash file"
} else {
"Discard changes"
};
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
.action("Stage File", ToggleStaged.boxed_clone())
.action(revert_title, editor::actions::RevertFile.boxed_clone())
.separator()
.action("Open Diff", Confirm.boxed_clone())
.action("Open File", SecondaryConfirm.boxed_clone())
});
let subscription = cx.subscribe_in(
&context_menu,
window,
|this, _, _: &DismissEvent, window, cx| {
if this.context_menu.as_ref().is_some_and(|context_menu| {
context_menu.0.focus_handle(cx).contains_focused(window, cx)
}) {
cx.focus_self(window);
}
this.context_menu.take();
cx.notify();
},
);
self.selected_entry = Some(ix);
self.context_menu = Some((context_menu, position, subscription));
cx.notify();
}
fn render_entry(
&self,
ix: usize,
@@ -1764,26 +1865,31 @@ impl GitPanel {
cx.stop_propagation();
});
let id = ElementId::Name(format!("entry_{}", display_name).into());
div()
.w_full()
.child(
ListItem::new(id)
.indent_level(1)
.indent_step_size(Checkbox::container_size(cx).to_pixels(window.rem_size()))
ListItem::new(ix)
.spacing(ListItemSpacing::Sparse)
.start_slot(start_slot)
.toggle_state(selected)
.focused(selected && self.focus_handle(cx).is_focused(window))
.disabled(!has_write_access)
.on_click({
cx.listener(move |this, _, window, cx| {
cx.listener(move |this, event: &ClickEvent, window, cx| {
this.selected_entry = Some(ix);
cx.notify();
this.open_selected(&Default::default(), window, cx);
if event.modifiers().secondary() {
this.open_file(&Default::default(), window, cx)
} else {
this.open_diff(&Default::default(), window, cx);
}
})
})
.on_secondary_mouse_down(cx.listener(
move |this, event: &MouseDownEvent, window, cx| {
this.deploy_context_menu(event.position, ix, window, cx)
},
))
.child(
h_flex()
.when_some(repo_path.parent(), |this, parent| {
@@ -1845,19 +1951,19 @@ impl Render for GitPanel {
}))
.on_action(cx.listener(GitPanel::commit))
})
.when(self.is_focused(window, cx), |this| {
this.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::close_panel))
})
.on_action(cx.listener(Self::open_selected))
.on_action(cx.listener(Self::select_first))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::close_panel))
.on_action(cx.listener(Self::open_diff))
.on_action(cx.listener(Self::open_file))
.on_action(cx.listener(Self::revert))
.on_action(cx.listener(Self::focus_changes_list))
.on_action(cx.listener(Self::focus_editor))
.on_action(cx.listener(Self::toggle_staged_for_selected))
.when(has_write_access && has_co_authors, |git_panel| {
git_panel.on_action(cx.listener(Self::fill_co_authors))
git_panel.on_action(cx.listener(Self::toggle_fill_co_authors))
})
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
.on_hover(cx.listener(|this, hovered, window, cx| {
@@ -1881,6 +1987,15 @@ impl Render for GitPanel {
})
.children(self.render_previous_commit(cx))
.child(self.render_commit_editor(window, cx))
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
deferred(
anchored()
.position(*position)
.anchor(gpui::Corner::TopLeft)
.child(menu.clone()),
)
.with_priority(1)
}))
}
}

View File

@@ -622,41 +622,6 @@ impl HighlightedText {
gpui::StyledText::new(self.text.clone())
.with_highlights(default_style, self.highlights.iter().cloned())
}
/// Returns the first line without leading whitespace unless highlighted
/// and a boolean indicating if there are more lines after
pub fn first_line_preview(self) -> (Self, bool) {
let newline_ix = self.text.find('\n').unwrap_or(self.text.len());
let first_line = &self.text[..newline_ix];
// Trim leading whitespace, unless an edit starts prior to it.
let mut preview_start_ix = first_line.len() - first_line.trim_start().len();
if let Some((first_highlight_range, _)) = self.highlights.first() {
preview_start_ix = preview_start_ix.min(first_highlight_range.start);
}
let preview_text = &first_line[preview_start_ix..];
let preview_highlights = self
.highlights
.into_iter()
.take_while(|(range, _)| range.start < newline_ix)
.filter_map(|(mut range, highlight)| {
range.start = range.start.saturating_sub(preview_start_ix);
range.end = range.end.saturating_sub(preview_start_ix).min(newline_ix);
if range.is_empty() {
None
} else {
Some((range, highlight))
}
});
let preview = Self {
text: SharedString::new(preview_text),
highlights: preview_highlights.collect(),
};
(preview, self.text.len() > newline_ix)
}
}
impl HighlightedTextBuilder {

View File

@@ -62,7 +62,7 @@ impl CloudModel {
pub fn availability(&self) -> LanguageModelAvailability {
match self {
Self::Anthropic(model) => match model {
anthropic::Model::Claude3_5Sonnet | anthropic::Model::Claude3_7Sonnet => {
anthropic::Model::Claude3_5Sonnet => {
LanguageModelAvailability::RequiresPlan(Plan::Free)
}
anthropic::Model::Claude3Opus

View File

@@ -295,10 +295,6 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
anthropic::Model::Claude3_5Sonnet.id().to_string(),
CloudModel::Anthropic(anthropic::Model::Claude3_5Sonnet),
);
models.insert(
anthropic::Model::Claude3_7Sonnet.id().to_string(),
CloudModel::Anthropic(anthropic::Model::Claude3_7Sonnet),
);
}
let llm_closed_beta_models = if cx.has_flag::<LlmClosedBeta>() {

View File

@@ -25,7 +25,6 @@ use strum::IntoEnumIterator;
use ui::prelude::*;
use super::anthropic::count_anthropic_tokens;
use super::google::count_google_tokens;
use super::open_ai::count_open_ai_tokens;
const PROVIDER_ID: &str = "copilot_chat";
@@ -175,19 +174,13 @@ impl LanguageModel for CopilotChatLanguageModel {
) -> BoxFuture<'static, Result<usize>> {
match self.model {
CopilotChatModel::Claude3_5Sonnet => count_anthropic_tokens(request, cx),
CopilotChatModel::Claude3_7Sonnet => count_anthropic_tokens(request, cx),
CopilotChatModel::Gemini20Flash => count_google_tokens(request, cx),
_ => {
let model = match self.model {
CopilotChatModel::Gpt4o => open_ai::Model::FourOmni,
CopilotChatModel::Gpt4 => open_ai::Model::Four,
CopilotChatModel::Gpt3_5Turbo => open_ai::Model::ThreePointFiveTurbo,
CopilotChatModel::O1 | CopilotChatModel::O3Mini => open_ai::Model::Four,
CopilotChatModel::Claude3_5Sonnet
| CopilotChatModel::Claude3_7Sonnet
| CopilotChatModel::Gemini20Flash => {
unreachable!()
}
CopilotChatModel::Claude3_5Sonnet => unreachable!(),
};
count_open_ai_tokens(request, model, cx)
}

View File

@@ -11,7 +11,7 @@ use language_model::LanguageModelCompletionEvent;
use language_model::{
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
LanguageModelRequest, RateLimiter, Role,
LanguageModelRequest, RateLimiter,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -324,36 +324,6 @@ impl LanguageModel for GoogleLanguageModel {
}
}
pub fn count_google_tokens(
request: LanguageModelRequest,
cx: &App,
) -> BoxFuture<'static, Result<usize>> {
// We couldn't use the GoogleLanguageModelProvider to count tokens because the github copilot doesn't have the access to google_ai directly.
// So we have to use tokenizer from tiktoken_rs to count tokens.
cx.background_executor()
.spawn(async move {
let messages = request
.messages
.into_iter()
.map(|message| tiktoken_rs::ChatCompletionRequestMessage {
role: match message.role {
Role::User => "user".into(),
Role::Assistant => "assistant".into(),
Role::System => "system".into(),
},
content: Some(message.string_contents()),
name: None,
function_call: None,
})
.collect::<Vec<_>>();
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
})
.boxed()
}
struct ConfigurationView {
api_key_editor: Entity<Editor>,
state: gpui::Entity<State>,

View File

@@ -461,17 +461,6 @@ fn rename_context_key(
}
}
/// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from_iter([
("inline_completion", "edit_prediction"),
(
"inline_completion_requires_modifier",
"edit_prediction_requires_modifier",
),
])
});
const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document
(array
(object
@@ -497,10 +486,15 @@ const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document
(#eq? @name "bindings")
)"#;
fn is_snake_case(text: &str) -> bool {
text == text.to_case(Case::Snake)
}
fn to_snake_case(text: &str) -> String {
text.to_case(Case::Snake)
}
/// [ "editor::FoldAtLevel", { "SomeKey": "Value" } ] -> [ "editor::FoldAtLevel", { "some_key" : "value" } ]
fn action_argument_snake_case(
contents: &str,
mat: &QueryMatch,
@@ -516,26 +510,34 @@ fn action_argument_snake_case(
.byte_range(),
)?;
let replacement_key = ACTION_ARGUMENT_SNAKE_CASE_REPLACE.get(action_name)?;
let argument_key = contents.get(
mat.nodes_for_capture_index(argument_key_ix)
.next()?
.byte_range(),
)?;
if argument_key != *replacement_key {
return None;
}
let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?;
let argument_value = contents.get(argument_value_node.byte_range())?;
let new_key = to_snake_case(argument_key);
let new_value = if argument_value_node.kind() == "string" {
format!("\"{}\"", to_snake_case(argument_value.trim_matches('"')))
} else {
argument_value.to_string()
};
let mut needs_replacement = false;
let mut new_key = argument_key.to_string();
if !is_snake_case(argument_key) {
new_key = to_snake_case(argument_key);
needs_replacement = true;
}
let mut new_value = argument_value.to_string();
if argument_value_node.kind() == "string" {
let inner_value = argument_value.trim_matches('"');
if !is_snake_case(inner_value) {
new_value = format!("\"{}\"", to_snake_case(inner_value));
needs_replacement = true;
}
}
if !needs_replacement {
return None;
}
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
let replacement = format!(
@@ -546,27 +548,16 @@ fn action_argument_snake_case(
Some((range_to_replace, replacement))
}
pub static ACTION_ARGUMENT_SNAKE_CASE_REPLACE: LazyLock<HashMap<&str, &str>> =
LazyLock::new(|| {
HashMap::from_iter([
("vim::NextWordStart", "ignorePunctuation"),
("vim::NextWordEnd", "ignorePunctuation"),
("vim::PreviousWordStart", "ignorePunctuation"),
("vim::PreviousWordEnd", "ignorePunctuation"),
("vim::MoveToNext", "partialWord"),
("vim::MoveToPrev", "partialWord"),
("vim::Down", "displayLines"),
("vim::Up", "displayLines"),
("vim::EndOfLine", "displayLines"),
("vim::StartOfLine", "displayLines"),
("vim::FirstNonWhitespace", "displayLines"),
("pane::CloseActiveItem", "saveIntent"),
("vim::Paste", "preserveClipboard"),
("vim::Word", "ignorePunctuation"),
("vim::Subword", "ignorePunctuation"),
("vim::IndentObj", "includeBelow"),
])
});
/// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from_iter([
("inline_completion", "edit_prediction"),
(
"inline_completion_requires_modifier",
"edit_prediction_requires_modifier",
),
])
});
const SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[
(SETTINGS_STRING_REPLACE_QUERY, replace_setting_name),
@@ -873,10 +864,10 @@ mod tests {
[
{
"bindings": {
"cmd-1": ["vim::PushOperator", { "Object": { "around": false } }],
"cmd-3": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
"cmd-2": ["vim::NextWordStart", { "ignorePunctuation": true }],
"cmd-4": ["task::Spawn", { "task_name": "a b" }] // should remain as it is
"cmd-1": ["vim::PushOperator", { "Object": { "SomeKey": "Value" } }],
"cmd-2": ["vim::SomeOtherAction", { "OtherKey": "Value" }],
"cmd-3": ["vim::SomeDifferentAction", { "OtherKey": true }],
"cmd-4": ["vim::OneMore", { "OtherKey": 4 }]
}
}
]
@@ -886,10 +877,10 @@ mod tests {
[
{
"bindings": {
"cmd-1": ["vim::PushObject", { "around": false }],
"cmd-3": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
"cmd-2": ["vim::NextWordStart", { "ignore_punctuation": true }],
"cmd-4": ["task::Spawn", { "task_name": "a b" }] // should remain as it is
"cmd-1": ["vim::PushObject", { "some_key": "value" }],
"cmd-2": ["vim::SomeOtherAction", { "other_key": "value" }],
"cmd-3": ["vim::SomeDifferentAction", { "other_key": true }],
"cmd-4": ["vim::OneMore", { "other_key": 4 }]
}
}
]

View File

@@ -142,15 +142,15 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::ThreePointFiveTurbo => 16_385,
Self::Four => 8_192,
Self::FourTurbo => 128_000,
Self::FourOmni => 128_000,
Self::FourOmniMini => 128_000,
Self::O1 => 200_000,
Self::O1Preview => 128_000,
Self::O1Mini => 128_000,
Self::O3Mini => 200_000,
Self::ThreePointFiveTurbo => 16385,
Self::Four => 8192,
Self::FourTurbo => 128000,
Self::FourOmni => 128000,
Self::FourOmniMini => 128000,
Self::O1 => 200000,
Self::O1Preview => 128000,
Self::O1Mini => 128000,
Self::O3Mini => 200000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}

View File

@@ -349,7 +349,7 @@ pub fn render_item<T>(
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_size: settings.buffer_font_size().into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.),
..Default::default()

View File

@@ -1896,21 +1896,13 @@ impl LocalLspStore {
if let Some(version) = version {
let buffer_id = buffer.read(cx).remote_id();
let snapshots = if let Some(snapshots) = self
let snapshots = self
.buffer_snapshots
.get_mut(&buffer_id)
.and_then(|m| m.get_mut(&server_id))
{
snapshots
} else if version == 0 {
// Some language servers report version 0 even if the buffer hasn't been opened yet.
// We detect this case and treat it as if the version was `None`.
return Ok(buffer.read(cx).text_snapshot());
} else {
return Err(anyhow!(
"no snapshots found for buffer {buffer_id} and server {server_id}"
));
};
.ok_or_else(|| {
anyhow!("no snapshots found for buffer {buffer_id} and server {server_id}")
})?;
let found_snapshot = snapshots
.binary_search_by_key(&version, |e| e.version)
@@ -8695,7 +8687,7 @@ fn ensure_uniform_list_compatible_label(label: &mut CodeLabel) {
}
_ => {
new_text.push(c);
new_idx += c.len_utf8();
new_idx += 1;
last_char_was_space = false;
}
}
@@ -8766,16 +8758,27 @@ fn ensure_uniform_list_compatible_label(label: &mut CodeLabel) {
}
#[cfg(test)]
mod tests {
use language::HighlightId;
#[test]
fn test_glob_literal_prefix() {
assert_eq!(glob_literal_prefix(Path::new("**/*.js")), Path::new(""));
assert_eq!(
glob_literal_prefix(Path::new("node_modules/**/*.js")),
Path::new("node_modules")
);
assert_eq!(
glob_literal_prefix(Path::new("foo/{bar,baz}.js")),
Path::new("foo")
);
assert_eq!(
glob_literal_prefix(Path::new("foo/bar/baz.js")),
Path::new("foo/bar/baz.js")
);
use super::*;
#[test]
fn test_glob_literal_prefix() {
assert_eq!(glob_literal_prefix(Path::new("**/*.js")), Path::new(""));
#[cfg(target_os = "windows")]
{
assert_eq!(glob_literal_prefix(Path::new("**\\*.js")), Path::new(""));
assert_eq!(
glob_literal_prefix(Path::new("node_modules/**/*.js")),
glob_literal_prefix(Path::new("node_modules\\**/*.js")),
Path::new("node_modules")
);
assert_eq!(
@@ -8783,43 +8786,8 @@ mod tests {
Path::new("foo")
);
assert_eq!(
glob_literal_prefix(Path::new("foo/bar/baz.js")),
glob_literal_prefix(Path::new("foo\\bar\\baz.js")),
Path::new("foo/bar/baz.js")
);
#[cfg(target_os = "windows")]
{
assert_eq!(glob_literal_prefix(Path::new("**\\*.js")), Path::new(""));
assert_eq!(
glob_literal_prefix(Path::new("node_modules\\**/*.js")),
Path::new("node_modules")
);
assert_eq!(
glob_literal_prefix(Path::new("foo/{bar,baz}.js")),
Path::new("foo")
);
assert_eq!(
glob_literal_prefix(Path::new("foo\\bar\\baz.js")),
Path::new("foo/bar/baz.js")
);
}
}
#[test]
fn test_multi_len_chars_normalization() {
let mut label = CodeLabel {
text: "myElˇ (parameter) myElˇ: {\n foo: string;\n}".to_string(),
runs: vec![(0..6, HighlightId(1))],
filter_range: 0..6,
};
ensure_uniform_list_compatible_label(&mut label);
assert_eq!(
label,
CodeLabel {
text: "myElˇ (parameter) myElˇ: { foo: string; }".to_string(),
runs: vec![(0..6, HighlightId(1))],
filter_range: 0..6,
}
);
}
}

View File

@@ -1612,6 +1612,16 @@ impl Project {
})
}
pub fn delete_file(
&mut self,
path: ProjectPath,
trash: bool,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
let entry = self.entry_for_path(&path, cx)?;
self.delete_entry(entry.id, trash, cx)
}
pub fn delete_entry(
&mut self,
entry_id: ProjectEntryId,

View File

@@ -59,7 +59,7 @@ fn init_logging_proxy() {
fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
struct MultiWrite {
file: Box<dyn std::io::Write + Send + 'static>,
file: std::fs::File,
channel: Sender<Vec<u8>>,
buffer: Vec<u8>,
}
@@ -80,14 +80,11 @@ fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
}
}
let log_file = Box::new(if log_file_path.exists() {
std::fs::OpenOptions::new()
.append(true)
.open(&log_file_path)
.context("Failed to open log file in append mode")?
} else {
std::fs::File::create(&log_file_path).context("Failed to create log file")?
});
let log_file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_file_path)
.context("Failed to open log file in append mode")?;
let (tx, rx) = smol::channel::unbounded();

View File

@@ -59,6 +59,7 @@ const DEFAULT_NUM_COLUMNS: usize = 128;
pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle {
let settings = ThemeSettings::get_global(cx).clone();
let font_size = settings.buffer_font_size().into();
let font_family = settings.buffer_font.family;
let font_features = settings.buffer_font.features;
let font_weight = settings.buffer_font.weight;
@@ -71,7 +72,7 @@ pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle {
font_features,
font_weight,
font_fallbacks,
font_size: theme::get_buffer_font_size(cx).into(),
font_size,
font_style: FontStyle::Normal,
line_height: window.line_height().into(),
background_color: Some(theme.colors().terminal_ansi_background),

View File

@@ -555,7 +555,7 @@ impl TerminalElement {
fn rem_size(&self, cx: &mut App) -> Option<Pixels> {
let settings = ThemeSettings::get_global(cx).clone();
let buffer_font_size = settings.buffer_font_size(cx);
let buffer_font_size = settings.buffer_font_size();
let rem_size_scale = {
// Our default UI font size is 14px on a 16px base scale.
// This means the default UI font size is 0.875rems.
@@ -619,7 +619,7 @@ impl Element for TerminalElement {
let hitbox = hitbox.unwrap();
let settings = ThemeSettings::get_global(cx).clone();
let buffer_font_size = settings.buffer_font_size(cx);
let buffer_font_size = settings.buffer_font_size();
let terminal_settings = TerminalSettings::get_global(cx);
@@ -646,8 +646,7 @@ impl Element for TerminalElement {
let line_height = terminal_settings.line_height.value();
let font_size = terminal_settings.font_size;
let font_size =
font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx));
let font_size = font_size.unwrap_or(buffer_font_size);
let theme = cx.theme().clone();

View File

@@ -6,8 +6,7 @@ use crate::{
use anyhow::Result;
use derive_more::{Deref, DerefMut};
use gpui::{
px, App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
Subscription, Window,
px, App, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels, Window,
};
use refineable::Refineable;
use schemars::{
@@ -235,16 +234,6 @@ impl SystemAppearance {
}
}
#[derive(Default)]
pub(crate) struct AdjustedBufferFontSize(Pixels);
impl Global for AdjustedBufferFontSize {}
#[derive(Default)]
pub(crate) struct AdjustedUiFontSize(Pixels);
impl Global for AdjustedUiFontSize {}
/// Represents the selection of a theme, which can be either static or dynamic.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(untagged)]
@@ -474,7 +463,7 @@ impl ThemeSettingsContent {
*icon_theme_to_update = icon_theme_name.to_string();
} else {
self.icon_theme = Some(IconThemeSelection::Static(icon_theme_name.to_string()));
self.theme = Some(ThemeSelection::Static(icon_theme_name.to_string()));
}
}
@@ -556,11 +545,13 @@ impl BufferLineHeight {
impl ThemeSettings {
/// Returns the buffer font size.
pub fn buffer_font_size(&self, cx: &App) -> Pixels {
let font_size = cx
.try_global::<AdjustedBufferFontSize>()
.map_or(self.buffer_font_size, |size| size.0);
clamp_font_size(font_size)
pub fn buffer_font_size(&self) -> Pixels {
Self::clamp_font_size(self.buffer_font_size)
}
/// Ensures that the font size is within the valid range.
pub fn clamp_font_size(size: Pixels) -> Pixels {
size.max(MIN_FONT_SIZE)
}
// TODO: Rename: `line_height` -> `buffer_line_height`
@@ -635,110 +626,19 @@ impl ThemeSettings {
}
}
/// Observe changes to the adjusted buffer font size.
pub fn observe_buffer_font_size_adjustment<V: 'static>(
cx: &mut Context<V>,
f: impl 'static + Fn(&mut V, &mut Context<V>),
) -> Subscription {
cx.observe_global::<AdjustedBufferFontSize>(f)
}
/// Sets the adjusted buffer font size.
pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
let adjusted_font_size = if let Some(AdjustedBufferFontSize(adjusted_size)) =
cx.try_global::<AdjustedBufferFontSize>()
{
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
let delta = *adjusted_size - buffer_font_size;
size + delta
} else {
size
};
clamp_font_size(adjusted_font_size)
}
/// Returns the adjusted buffer font size.
pub fn get_buffer_font_size(cx: &App) -> Pixels {
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
cx.try_global::<AdjustedBufferFontSize>()
.map_or(buffer_font_size, |adjusted_size| adjusted_size.0)
}
/// Adjusts the buffer font size.
pub fn adjust_buffer_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
let mut adjusted_size = cx
.try_global::<AdjustedBufferFontSize>()
.map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
f(&mut adjusted_size);
cx.set_global(AdjustedBufferFontSize(clamp_font_size(adjusted_size)));
cx.refresh_windows();
}
/// Returns whether the buffer font size has been adjusted.
pub fn has_adjusted_buffer_font_size(cx: &App) -> bool {
cx.has_global::<AdjustedBufferFontSize>()
}
/// Resets the buffer font size to the default value.
pub fn reset_buffer_font_size(cx: &mut App) {
if cx.has_global::<AdjustedBufferFontSize>() {
cx.remove_global::<AdjustedBufferFontSize>();
cx.refresh_windows();
}
}
// TODO: Make private, change usages to use `get_ui_font_size` instead.
#[allow(missing_docs)]
pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
let font = theme_settings.ui_font.clone();
(font, get_ui_font_size(cx))
(font, theme_settings.ui_font_size)
};
window.set_rem_size(ui_font_size);
ui_font
}
/// Gets the adjusted UI font size.
pub fn get_ui_font_size(cx: &App) -> Pixels {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
cx.try_global::<AdjustedUiFontSize>()
.map_or(ui_font_size, |adjusted_size| adjusted_size.0)
}
/// Sets the adjusted UI font size.
pub fn adjust_ui_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let mut adjusted_size = cx
.try_global::<AdjustedUiFontSize>()
.map_or(ui_font_size, |adjusted_size| adjusted_size.0);
f(&mut adjusted_size);
cx.set_global(AdjustedUiFontSize(clamp_font_size(adjusted_size)));
cx.refresh_windows();
}
/// Returns whether the UI font size has been adjusted.
pub fn has_adjusted_ui_font_size(cx: &App) -> bool {
cx.has_global::<AdjustedUiFontSize>()
}
/// Resets the UI font size to the default value.
pub fn reset_ui_font_size(cx: &mut App) {
if cx.has_global::<AdjustedUiFontSize>() {
cx.remove_global::<AdjustedUiFontSize>();
cx.refresh_windows();
}
}
/// Ensures font size is within the valid range.
pub fn clamp_font_size(size: Pixels) -> Pixels {
size.max(MIN_FONT_SIZE)
}
fn clamp_font_weight(weight: f32) -> FontWeight {
FontWeight(weight.clamp(100., 950.))
}

View File

@@ -23,7 +23,6 @@ use std::path::Path;
use std::sync::Arc;
use ::settings::Settings;
use ::settings::SettingsStore;
use anyhow::Result;
use fallback_themes::apply_status_color_defaults;
use fs::Fs;
@@ -102,16 +101,6 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
ThemeSettings::register(cx);
FontFamilyCache::init_global(cx);
let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
cx.observe_global::<SettingsStore>(move |cx| {
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
if buffer_font_size != prev_buffer_font_size {
prev_buffer_font_size = buffer_font_size;
reset_buffer_font_size(cx);
}
})
.detach();
}
/// Implementing this trait allows accessing the active theme.

View File

@@ -83,7 +83,7 @@ pub trait StyledTypography: Styled + Sized {
/// or other places that text needs to match the user's buffer font size.
fn text_buffer(self, cx: &App) -> Self {
let settings = ThemeSettings::get_global(cx);
self.text_size(settings.buffer_font_size(cx))
self.text_size(settings.buffer_font_size())
}
}

View File

@@ -215,7 +215,7 @@ pub fn init(cx: &mut App) {
};
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
let desired_size = if let Some(count) = Vim::take_count(cx) {
height * count
@@ -233,7 +233,7 @@ pub fn init(cx: &mut App) {
};
let Ok(width) = window
.text_system()
.advance(font_id, theme.buffer_font_size(cx), 'm')
.advance(font_id, theme.buffer_font_size(), 'm')
else {
return;
};
@@ -248,7 +248,7 @@ pub fn init(cx: &mut App) {
};
let Ok(width) = window
.text_system()
.advance(font_id, theme.buffer_font_size(cx), 'm')
.advance(font_id, theme.buffer_font_size(), 'm')
else {
return;
};
@@ -258,14 +258,14 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, height * count, window, cx);
});
workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
});

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.174.8"
version = "0.175.0"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
stable
dev

View File

@@ -540,94 +540,65 @@ fn register_actions(
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::IncreaseUiFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let ui_font_size = theme::get_ui_font_size(cx) + px(1.0);
let _ = settings
.ui_font_size
.insert(theme::clamp_font_size(ui_font_size).0);
});
} else {
theme::adjust_ui_font_size(cx, |size| {
*size += px(1.0);
});
}
move |_, _: &zed_actions::IncreaseUiFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = ThemeSettings::clamp_font_size(
ThemeSettings::get_global(cx).ui_font_size + px(1.),
);
let _ = settings.ui_font_size.insert(buffer_font_size.into());
});
}
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::DecreaseUiFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let ui_font_size = theme::get_ui_font_size(cx) - px(1.0);
let _ = settings
.ui_font_size
.insert(theme::clamp_font_size(ui_font_size).0);
});
} else {
theme::adjust_ui_font_size(cx, |size| {
*size -= px(1.0);
});
}
move |_, _: &zed_actions::DecreaseUiFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = ThemeSettings::clamp_font_size(
ThemeSettings::get_global(cx).ui_font_size - px(1.),
);
let _ = settings.ui_font_size.insert(buffer_font_size.into());
});
}
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::ResetUiFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, _| {
settings.ui_font_size = None;
});
} else {
theme::reset_ui_font_size(cx);
}
move |_, _: &zed_actions::ResetUiFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, _| {
let _ = settings.ui_font_size.take();
});
}
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::IncreaseBufferFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = theme::get_buffer_font_size(cx) + px(1.0);
let _ = settings
.buffer_font_size
.insert(theme::clamp_font_size(buffer_font_size).0);
});
} else {
theme::adjust_buffer_font_size(cx, |size| {
*size += px(1.0);
});
}
move |_, _: &zed_actions::IncreaseBufferFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = ThemeSettings::clamp_font_size(
ThemeSettings::get_global(cx).buffer_font_size() + px(1.),
);
let _ = settings.buffer_font_size.insert(buffer_font_size.into());
});
}
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::DecreaseBufferFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = theme::get_buffer_font_size(cx) - px(1.0);
let _ = settings
.buffer_font_size
.insert(theme::clamp_font_size(buffer_font_size).0);
});
} else {
theme::adjust_buffer_font_size(cx, |size| {
*size -= px(1.0);
});
}
move |_, _: &zed_actions::DecreaseBufferFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, cx| {
let buffer_font_size = ThemeSettings::clamp_font_size(
ThemeSettings::get_global(cx).buffer_font_size() - px(1.),
);
let _ = settings.buffer_font_size.insert(buffer_font_size.into());
});
}
})
.register_action({
let fs = app_state.fs.clone();
move |_, action: &zed_actions::ResetBufferFontSize, _window, cx| {
if action.persist {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, _| {
settings.buffer_font_size = None;
});
} else {
theme::reset_buffer_font_size(cx);
}
move |_, _: &zed_actions::ResetBufferFontSize, _window, cx| {
update_settings_file::<ThemeSettings>(fs.clone(), cx, move |settings, _| {
let _ = settings.buffer_font_size.take();
});
}
})
.register_action(install_cli)

View File

@@ -131,18 +131,9 @@ pub fn app_menus() -> Vec<Menu> {
Menu {
name: "View".into(),
items: vec![
MenuItem::action(
"Zoom In",
zed_actions::IncreaseBufferFontSize { persist: true },
),
MenuItem::action(
"Zoom Out",
zed_actions::DecreaseBufferFontSize { persist: true },
),
MenuItem::action(
"Reset Zoom",
zed_actions::ResetBufferFontSize { persist: true },
),
MenuItem::action("Zoom In", zed_actions::IncreaseBufferFontSize),
MenuItem::action("Zoom Out", zed_actions::DecreaseBufferFontSize),
MenuItem::action("Reset Zoom", zed_actions::ResetBufferFontSize),
MenuItem::separator(),
MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock),
MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock),

View File

@@ -38,48 +38,6 @@ actions!(
Extensions,
OpenLicenses,
OpenTelemetryLog,
]
);
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct DecreaseBufferFontSize {
#[serde(default)]
pub persist: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct IncreaseBufferFontSize {
#[serde(default)]
pub persist: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct ResetBufferFontSize {
#[serde(default)]
pub persist: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct DecreaseUiFontSize {
#[serde(default)]
pub persist: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct IncreaseUiFontSize {
#[serde(default)]
pub persist: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct ResetUiFontSize {
#[serde(default)]
pub persist: bool,
}
impl_actions!(
zed,
[
DecreaseBufferFontSize,
IncreaseBufferFontSize,
ResetBufferFontSize,

View File

@@ -69,7 +69,7 @@ impl CompletionDiffElement {
let settings = ThemeSettings::get_global(cx).clone();
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_size: settings.buffer_font_size(cx).into(),
font_size: settings.buffer_font_size().into(),
font_family: settings.buffer_font.family,
font_features: settings.buffer_font.features,
font_fallbacks: settings.buffer_font.fallbacks,

View File

@@ -67,7 +67,7 @@ impl ZedPredictModal {
}
fn view_blog(&mut self, _: &ClickEvent, _: &mut Window, cx: &mut Context<Self>) {
cx.open_url("https://zed.dev/blog/edit-prediction");
cx.open_url("https://zed.dev/blog/edit-predictions");
cx.notify();
onboarding_event!("Blog Link clicked");

View File

@@ -30,46 +30,19 @@ On Linux, `alt-tab` is often used by the window manager for switching windows, s
{#action editor::AcceptPartialEditPrediction} ({#kb editor::AcceptPartialEditPrediction}) can be used to accept the current edit prediction up to the next word boundary.
todo! document editor::Cancel to discard
See the [Configuring GitHub Copilot](#github-copilot) and [Configuring Supermaven](#supermaven) sections below for configuration of other providers. Only text insertions at the current cursor are supported for these providers, whereas the Zeta model provides multiple predictions including deletions.
## Default Behavior of Tab
The default behavior of `tab` varies based on the context:
* todo!
## Configuring Edit Prediction Keybindings
By default, `tab` is used to accept edit predictions. You can use another keybinding by inserting this in your keymap:
```json
{
"context": "Editor && edit_prediction",
"bindings": {
// Here we also allow `alt-enter` to accept the prediction
"alt-enter": "editor::AcceptEditPrediction"
}
}
```
When you have both a language server completion and an edit prediction on screen at the same time, Zed uses a different context to accept keybindings (`edit_prediction_conflict`). If you want to use a different keybinding, you can insert this in your keymap:
```json
{
"context": "Editor && edit_prediction_conflict",
"bindings": {
"ctrl-enter": "editor::AcceptEditPrediction"
}
}
```
If your keybinding contains a modifier (`ctrl` in the example), it will be used to preview the edit prediction and temporarily hide the language server completion menu.
You can also bind a keystroke without a modifier. In that case, Zed will use the default modifier (`alt`) to preview the edit prediction.
```json
{
"context": "Editor && edit_prediction_conflict",
"bindings": {
// Here we bind tab to accept even when there's a language server completion
"tab": "editor::AcceptEditPrediction"
}
}
```
todo! document `edit_prediction_requires_modifier`
### Keybinding Example: Always Use Alt-Tab
@@ -122,15 +95,15 @@ While `tab` and `alt-tab` are supported on Linux, `alt-l` is displayed instead.
{
"context": "Editor && edit_prediction",
"bindings": {
"tab": "editor::AcceptEditPrediction",
"alt-tab": "editor::AcceptEditPrediction",
// Optional: This makes the default `alt-l` binding do nothing.
"alt-l": null
}
},
{
"context": "Editor && edit_prediction_conflict",
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
"alt-tab": "editor::AcceptEditPrediction",
"tab": "editor::AcceptEditPrediction",
// Optional: This makes the default `alt-l` binding do nothing.
"alt-l": null
}

View File

@@ -5,22 +5,19 @@ slug: subprocessors
This page provides information about the Subprocessors Zed has engaged to provide processing activities on Customer Data as defined in the [Zed Terms of Use](https://zed.dev/terms).
| Subprocessor | Purpose | Location |
| ------------------- | ------------------------ | ------------- |
| Cloudflare | Cloud Infrastructure | Worldwide |
| Amazon Web Services | Cloud Infrastructure | United States |
| DigitalOcean | Cloud Infrastructure | United States |
| Vercel | Cloud Infrastructure | United States |
| ConvertKit | Email Marketing | United States |
| Axiom | Analytics | United States |
| Hex Technologies | Analytics | United States |
| Snowflake | Analytics | United States |
| LiveKit | Audio/Video Conferencing | United States |
| GitHub | Authentication | United States |
| Anthropic | AI Services | United States |
| BaseTen | AI Services | United States |
| Fireworks AI | AI Services | United States |
| Google | AI Services | United States |
| OpenAI | AI Services | United States |
| Subprocessor | Purpose | Location |
| ------------ | -------------------- | ------------- |
| Cloudflare | Cloud Infrastructure | Worldwide |
| Vercel | Cloud Infrastructure | United States |
| DigitalOcean | Cloud Infrastructure | United States |
| AWS | Cloud Infrastructure | United States |
| ConvertKit | Email Marketing | United States |
| Axiom | Analytics | United States |
| Snowflake | Analytics | United States |
| Metabase | Analytics | United States |
| GitHub | Authentication | United States |
| LiveKit | Audio Conferencing | United States |
| Anthropic | AI Services | United States |
| OpenAI | AI Services | United States |
**DATE: February 13, 2025**
**DATE: December 9, 2024**

View File

@@ -7,7 +7,7 @@ PLEASE READ THESE TERMS AND CONDITIONS CAREFULLY BEFORE USING THE SERVICE OR SOF
## 1. ACCESS TO AND USE OF THE SOLUTION
Subject to the terms and conditions of this Agreement, Zed hereby grants to You, and You hereby accept from Zed, a term-limited, non-exclusive, non-transferable, non-assignable and non-sublicensable license to make use of the Editor for Your internal use only, and subject to the use limitations in Section 2.2.
Subject to the terms and conditions in this Agreement, Zed will make available to You the Zed Editor Software (the "Editor"), and the Zed Collaboration Service (the "Zed Service"). The Zed Service may be used in conjunction with the Editor (the Zed Service and Editor, collectively the "Solution"). The Zed Service is designed to allow for collaboration, but the use of the Zed Service is not required to make use of the Editor; Your use of the Zed Service is optional.
## 2. TERMS APPLICABLE TO THE EDITOR
@@ -35,7 +35,7 @@ You will use the Zed Service only in accordance with all applicable laws, includ
### 3.3. Customer Data
You are solely responsible for Customer Data including, but not limited to: (a) compliance with all applicable laws and this Agreement; (b) any claims relating to Customer Data; and (c) any claims that Customer Data infringes, misappropriates, or otherwise violates the rights of any third party. You agree and acknowledge that Customer Data may be irretrievably deleted if Your account is terminated. For purposes of this Agreement, "Customer Data" shall mean any data, information or other material provided, uploaded, or submitted by You to the Zed Service in the course of using the Zed Service. Notwithstanding anything to the contrary, You represent and warrant that You will not transfer or make available to Zed any personally identifiable information or related information subject to applicable data privacy laws or regulations, unless otherwise agreed to in writing by Zed.
You are solely responsible for Customer Data including, but not limited to: (a) compliance with all applicable laws and this Agreement; (b) any claims relating to Customer Data; and (c) any claims that Customer Data infringes, misappropriates, or otherwise violates the rights of any third party. You agree and acknowledge that Customer Data may be irretrievably deleted if Your account is terminated. For purposes of this Agreement, "Customer Data" shall mean any data, information or other material provided, uploaded, or submitted by You to the Zed Service in the course of using the Zed Service.
#### 3.3.1. Customer Data Made Available to Zed
@@ -60,41 +60,7 @@ Customer Data consisting of data related to the behavior of the Solution prior t
#### 3.3.4. User Content
• You may access, modify or create certain data or information in connection with your access or use of the Zed Editor or the Solution. Such data and information may include, but is not limited to any of the following:
- (a) file contents and associated metadata (e.g., filename, paths, size, timestamps);
- (b) source control history, comments and metadata (e.g., git history, commit messages);
- (c) configuration data (e.g., settings, keymaps);
- (d) anything typed, pasted and/or displayed on screen while using the Editor;
- (e) derivative works of the above generated by the Editor (e.g., format conversions, summaries, indexes, caches);
- (f) metadata, code and other derivative works of the above returned by language servers and other local tooling; and
- (g) metadata, code and other derivative works of the above returned by services integrated with the Zed Editor
(a-g collectively, "User Content").
#### 3.3.5 Handling of User Content
Zed will make use of or transfer User Content only as specified in this Agreement, or as necessary to comply with applicable law.
#### 3.3.5.1 Zed Collaboration Services
When using Zed Collaboration Services, User Content is transmitted from Your environment only if You collaborate with other Zed users by electing to share a project in the Editor. Once You share a project, Zed may transmit User Content consisting of file paths, file contents, and metadata regarding the code returned by language servers. Currently, Zed does not persist any User Content beyond the Your collaboration session. If You unshare a project or disconnect from the Solution, all information associated with such project will be deleted from Zed servers. In the future, Zed may save User Content regarding projects beyond the scope of a single collaboration session. We may share such User Content with those users You elected to grant access to. Zed's access to such User Content is limited to debugging and making improvements to the Solution.
#### 3.3.5.2 Other Services
The Zed Editor supports integration with API-based services maintained and not operated by Zed (the "Other Services"). By way of example, Other Services includes those made available by GitHub, Anthropic, OpenAI, and similar providers, or those You host or manage directly. You may configure the Zed Editor to interoperate, communicate with, and exchange data (including User Content) directly with the Other Services. . Zed is not responsible or otherwise liable with respect to Your use of any Other Service, including but not limited to the exchange of data between the Other Service and the Zed Editor. The terms and conditions, including the applicable privacy policy, with respect to the Other Service are those made available by the applicable Other Service, not these Terms of Use.
#### 3.3.5.3 Zed AI Services
The Zed Editor supports integration with API-based services maintained and operated by Zed (the "Zed AI Services"). You may elect to use Zed AI Services as the provider for various Zed Editor features (e.g., AI Assistant Panel, Code Completions, Edit Predictions, and similar features). In connection with Your use of these features, the Zed Editor and Zed AI Services may make use of User Content to generate contextually relevant responses (the “Output”). Other than as specified in Section 3.3.5.4 of these Terms of Use, Zed will not use User Content for training of its models, or disclose User Content.
Output is provided "as is" without any warranties or guarantees of functionality, security, or fitness for a particular purpose. While efforts are made to ensure the accuracy and reliability, Output may include errors, vulnerabilities, and defects. You are responsible for reviewing, testing, and validating Output before use in any production or critical environment. Zed assumes no liability for any damages, losses, or liability arising from the use, modification, reliance on, or deployment of Output. Any such use is at Your own risk.
#### 3.3.5.4 Model Improvement Feedback
When using Zed AI Services to provide Edit Predictions in connection with certain open source software projects, You may elect to share requests, responses and feedback comments (collectively "Model Improvement Feedback") with Zed, and Zed may use the same to improve Zed Edit Predictions models. You may opt-out of sharing Model Improvement Feedback at any time.
For more information on Zed Edit Predictions please see: [https://zed.dev/docs/model-improvement](https://zed.dev/docs/model-improvement)
Customer Data consisting of User content created while using the Solution is classified as "User Content". User Content is transmitted from Your environment only if You collaborate with other Zed users by electing to share a project in the Editor. Once You share a project, Zed may transmit User Content consisting of file paths, file contents, and metadata regarding the code returned by language servers. Currently, Zed does not persist any User Content beyond the Your collaboration session. If You unshare a project or disconnect from the Solution, all information associated with such project will be deleted from Zed servers. In the future, Zed may save User Content regarding projects beyond the scope of a single collaboration session. We may share such User Content with those users You elected to grant access to. Zed's access to such User Content is limited to debugging and making improvements to the Solution.
#### 3.3.5. Privacy Policy
@@ -172,4 +138,4 @@ Zed reserves the right to update this Agreement at any time. The terms and condi
This Agreement is the complete and exclusive statement of the mutual understanding of the parties and supersedes and cancels all previous written and oral agreements, communications, and other understandings relating to the subject matter of this Agreement, and all waivers and modifications must be in a writing signed by both parties, except as otherwise provided herein. Any term or provision of this Agreement held to be illegal or unenforceable shall be, to the fullest extent possible, interpreted so as to be construed as valid, but in any event the validity or enforceability of the remainder hereof shall not be affected.
**DATE: February 13, 2025**
**DATE: August 19, 2024**

View File

@@ -118,7 +118,7 @@ mv Cargo.toml.backup Cargo.toml
popd
echo "Bundled ${app_path}"
if [[ -n "${MACOS_CERTIFICATE:-}" && -n "${MACOS_CERTIFICATE_PASSWORD:-}" && -n "${APPLE_NOTARIZATION_KEY:-}" && -n "${APPLE_NOTARIZATION_KEY_ID:-}" && -n "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then
if [[ -n "${MACOS_CERTIFICATE:-}" && -n "${MACOS_CERTIFICATE_PASSWORD:-}" && -n "${APPLE_NOTARIZATION_USERNAME:-}" && -n "${APPLE_NOTARIZATION_PASSWORD:-}" ]]; then
can_code_sign=true
echo "Setting up keychain for code signing..."
@@ -247,7 +247,7 @@ function sign_app_binaries() {
/usr/bin/codesign --deep --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "$IDENTITY" "${app_path}/Contents/MacOS/zed" -v
/usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "$IDENTITY" "${app_path}" -v
else
echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_KEY, APPLE_NOTARIZATION_KEY_ID, APPLE_NOTARIZATION_ISSUER_ID"
echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD"
if [[ "$local_only" = false ]]; then
echo "To create a self-signed local build use ./scripts/build.sh -ldf"
exit 1
@@ -311,7 +311,6 @@ function sign_app_binaries() {
rm -rf ${dmg_source_directory}
mkdir -p ${dmg_source_directory}
mv "${app_path}" "${dmg_source_directory}"
notarization_key_file=$(mktemp)
if [[ $can_code_sign = true ]]; then
echo "Creating temporary DMG at ${dmg_file_path} using ${dmg_source_directory} to notarize app bundle"
@@ -321,8 +320,7 @@ function sign_app_binaries() {
/usr/bin/codesign --deep --force --timestamp --options runtime --sign "$IDENTITY" "$(pwd)/${dmg_file_path}" -v
echo "Notarizing DMG with Apple"
echo "$APPLE_NOTARIZATION_KEY" > "$notarization_key_file"
"${xcode_bin_dir_path}/notarytool" submit --wait --key "$notarization_key_file" --key-id "$APPLE_NOTARIZATION_KEY_ID" --issuer "$APPLE_NOTARIZATION_ISSUER_ID" "${dmg_file_path}"
"${xcode_bin_dir_path}/notarytool" submit --wait --apple-id "$APPLE_NOTARIZATION_USERNAME" --password "$APPLE_NOTARIZATION_PASSWORD" --team-id "$APPLE_NOTARIZATION_TEAM" "${dmg_file_path}"
echo "Removing temporary DMG (used only for notarization)"
rm "${dmg_file_path}"
@@ -349,9 +347,8 @@ function sign_app_binaries() {
if [[ $can_code_sign = true ]]; then
echo "Notarizing DMG with Apple"
/usr/bin/codesign --deep --force --timestamp --options runtime --sign "$IDENTITY" "$(pwd)/${dmg_file_path}" -v
"${xcode_bin_dir_path}/notarytool" submit --wait --key "$notarization_key_file" --key-id "$APPLE_NOTARIZATION_KEY_ID" --issuer "$APPLE_NOTARIZATION_ISSUER_ID" "${dmg_file_path}"
"${xcode_bin_dir_path}/notarytool" submit --wait --apple-id "$APPLE_NOTARIZATION_USERNAME" --password "$APPLE_NOTARIZATION_PASSWORD" --team-id "$APPLE_NOTARIZATION_TEAM" "${dmg_file_path}"
"${xcode_bin_dir_path}/stapler" staple "${dmg_file_path}"
rm "$notarization_key_file"
fi
if [ "$open_result" = true ]; then

View File

@@ -1,10 +1,9 @@
{\rtf1\ansi\ansicpg1252\cocoartf2821
{\rtf1\ansi\ansicpg1252\cocoartf2761
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\froman\fcharset0 Times-Roman;\f1\froman\fcharset0 Times-Bold;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red0\green0\blue233;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c0\c93333;}
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{lower-alpha\}}{\leveltext\leveltemplateid2\'01\'01;}{\levelnumbers\'01;}\fi-360\li1440\lin1440 }{\listname ;}\listid1}
{\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{lower-alpha\}}{\leveltext\leveltemplateid102\'01\'01;}{\levelnumbers\'01;}\fi-360\li1440\lin1440 }{\listname ;}\listid2}}
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}}
{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{lower-alpha\}}{\leveltext\leveltemplateid2\'01\'01;}{\levelnumbers\'01;}\fi-360\li1440\lin1440 }{\listname ;}\listid1}}
{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
\deftab720
\pard\pardeftab720\sa240\partightenfactor0
@@ -15,7 +14,7 @@
\f1\b\fs36 \cf0 1. ACCESS TO AND USE OF THE SOLUTION\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0\fs24 \cf0 Subject to the terms and conditions of this Agreement, Zed hereby grants to You, and You hereby accept from Zed, a term-limited, non-exclusive, non-transferable, non-assignable and non-sublicensable license to make use of the Editor for Your internal use only, and subject to the use limitations in Section 2.2.\
\f0\b0\fs24 \cf0 Subject to the terms and conditions in this Agreement, Zed will make available to You the Zed Editor Software (the "Editor"), and the Zed Collaboration Service (the "Zed Service"). The Zed Service may be used in conjunction with the Editor (the Zed Service and Editor, collectively the "Solution"). The Zed Service is designed to allow for collaboration, but the use of the Zed Service is not required to make use of the Editor; Your use of the Zed Service is optional.\
\pard\pardeftab720\sa298\partightenfactor0
\f1\b\fs36 \cf0 2. TERMS APPLICABLE TO THE EDITOR\
@@ -57,7 +56,7 @@
\f1\b\fs28 \cf0 3.3. Customer Data\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0\fs24 \cf0 You are solely responsible for Customer Data including, but not limited to: (a) compliance with all applicable laws and this Agreement; (b) any claims relating to Customer Data; and (c) any claims that Customer Data infringes, misappropriates, or otherwise violates the rights of any third party. You agree and acknowledge that Customer Data may be irretrievably deleted if Your account is terminated. For purposes of this Agreement, "Customer Data" shall mean any data, information or other material provided, uploaded, or submitted by You to the Zed Service in the course of using the Zed Service. Notwithstanding anything to the contrary, You represent and warrant that You will not transfer or make available to Zed any personally identifiable information or related information subject to applicable data privacy laws or regulations, unless otherwise agreed to in writing by Zed.\
\f0\b0\fs24 \cf0 You are solely responsible for Customer Data including, but not limited to: (a) compliance with all applicable laws and this Agreement; (b) any claims relating to Customer Data; and (c) any claims that Customer Data infringes, misappropriates, or otherwise violates the rights of any third party. You agree and acknowledge that Customer Data may be irretrievably deleted if Your account is terminated. For purposes of this Agreement, "Customer Data" shall mean any data, information or other material provided, uploaded, or submitted by You to the Zed Service in the course of using the Zed Service.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.1. Customer Data Made Available to Zed\
@@ -96,62 +95,7 @@ Usage Data is associated with a secure random telemetry ID which may be linked t
\f1\b \cf0 3.3.4. User Content\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 \'e2\'80\'a2 You may access, modify or create certain data or information in connection with your access or use of the Zed Editor or the Solution. Such data and information may include, but is not limited to any of the following:\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext a }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 file contents and associated metadata (e.g., filename, paths, size, timestamps);\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext b }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 source control history, comments and metadata (e.g., git history, commit messages);\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext c }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 configuration data (e.g., settings, keymaps);\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext d }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 anything typed, pasted and/or displayed on screen while using the Editor;\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext e }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 derivative works of the above generated by the Editor (e.g., format conversions, summaries, indexes, caches);\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext f }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 metadata, code and other derivative works of the above returned by language servers and other local tooling; and\
\pard\tx940\tx1440\pardeftab720\li1440\fi-1440\partightenfactor0
\ls2\ilvl1\cf0 \kerning1\expnd0\expndtw0 \outl0\strokewidth0 {\listtext g }\expnd0\expndtw0\kerning0
\outl0\strokewidth0 \strokec2 metadata, code and other derivative works of the above returned by services integrated with the Zed Editor\
\pard\pardeftab720\sa240\partightenfactor0
\cf0 (a-g collectively, "User Content").\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5 Handling of User Content\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 Zed will make use of or transfer User Content only as specified in this Agreement, or as necessary to comply with applicable law.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5.1 Zed Collaboration Services\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 When using Zed Collaboration Services, User Content is transmitted from Your environment only if You collaborate with other Zed users by electing to share a project in the Editor. Once You share a project, Zed may transmit User Content consisting of file paths, file contents, and metadata regarding the code returned by language servers. Currently, Zed does not persist any User Content beyond the Your collaboration session. If You unshare a project or disconnect from the Solution, all information associated with such project will be deleted from Zed servers. In the future, Zed may save User Content regarding projects beyond the scope of a single collaboration session. We may share such User Content with those users You elected to grant access to. Zed's access to such User Content is limited to debugging and making improvements to the Solution.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5.2 Other Services\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 The Zed Editor supports integration with API-based services maintained and not operated by Zed (the "Other Services"). By way of example, Other Services includes those made available by GitHub, Anthropic, OpenAI, and similar providers, or those You host or manage directly. You may configure the Zed Editor to interoperate, communicate with, and exchange data (including User Content) directly with the Other Services. . Zed is not responsible or otherwise liable with respect to Your use of any Other Service, including but not limited to the exchange of data between the Other Service and the Zed Editor. The terms and conditions, including the applicable privacy policy, with respect to the Other Service are those made available by the applicable Other Service, not these Terms of Use.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5.3 Zed AI Services\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 The Zed Editor supports integration with API-based services maintained and operated by Zed (the "Zed AI Services"). You may elect to use Zed AI Services as the provider for various Zed Editor features (e.g., AI Assistant Panel, Code Completions, Edit Predictions, and similar features). In connection with Your use of these features, the Zed Editor and Zed AI Services may make use of User Content to generate contextually relevant responses (the \'e2\'80\'9cOutput\'e2\'80\uc0\u157 ). Other than as specified in Section 3.3.5.4 of these Terms of Use, Zed will not use User Content for training of its models, or disclose User Content.\
Output is provided "as is" without any warranties or guarantees of functionality, security, or fitness for a particular purpose. While efforts are made to ensure the accuracy and reliability, Output may include errors, vulnerabilities, and defects. You are responsible for reviewing, testing, and validating Output before use in any production or critical environment. Zed assumes no liability for any damages, losses, or liability arising from the use, modification, reliance on, or deployment of Output. Any such use is at Your own risk.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5.4 Model Improvement Feedback\
\pard\pardeftab720\sa240\partightenfactor0
\f0\b0 \cf0 When using Zed AI Services to provide Edit Predictions in connection with certain open source software projects, You may elect to share requests, responses and feedback comments (collectively "Model Improvement Feedback") with Zed, and Zed may use the same to improve Zed Edit Predictions models. You may opt-out of sharing Model Improvement Feedback at any time.\
For more information on Zed Edit Predictions please see: {\field{\*\fldinst{HYPERLINK "https://zed.dev/docs/model-improvement"}}{\fldrslt \cf3 \ul \ulc3 \strokec3 https://zed.dev/docs/model-improvement}}\
\f0\b0 \cf0 Customer Data consisting of User content created while using the Solution is classified as "User Content". User Content is transmitted from Your environment only if You collaborate with other Zed users by electing to share a project in the Editor. Once You share a project, Zed may transmit User Content consisting of file paths, file contents, and metadata regarding the code returned by language servers. Currently, Zed does not persist any User Content beyond the Your collaboration session. If You unshare a project or disconnect from the Solution, all information associated with such project will be deleted from Zed servers. In the future, Zed may save User Content regarding projects beyond the scope of a single collaboration session. We may share such User Content with those users You elected to grant access to. Zed's access to such User Content is limited to debugging and making improvements to the Solution.\
\pard\pardeftab720\sa319\partightenfactor0
\f1\b \cf0 3.3.5. Privacy Policy\
@@ -268,6 +212,6 @@ For more information on Zed Edit Predictions please see: {\field{\*\fldinst{HYPE
\f0\b0\fs24 \cf0 This Agreement is the complete and exclusive statement of the mutual understanding of the parties and supersedes and cancels all previous written and oral agreements, communications, and other understandings relating to the subject matter of this Agreement, and all waivers and modifications must be in a writing signed by both parties, except as otherwise provided herein. Any term or provision of this Agreement held to be illegal or unenforceable shall be, to the fullest extent possible, interpreted so as to be construed as valid, but in any event the validity or enforceability of the remainder hereof shall not be affected.\
\pard\pardeftab720\sa240\partightenfactor0
\f1\b \cf0 DATE: February 13, 2025
\f1\b \cf0 DATE: August 19, 2024
\f0\b0 \
}