Compare commits
1 Commits
settings-u
...
windows/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aa15aaaa0 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -862,16 +862,16 @@ jobs:
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: script/bundle-windows.ps1
|
||||
|
||||
- name: Upload installer (x86_64) to Workflow - zed (run-bundling)
|
||||
- name: Upload installer (x86_64) to Workflow - zed (main branch preview or run-bundling)
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
if: (${{ github.ref == 'refs/heads/main' }} && ${{ env.RELEASE_CHANNEL == 'preview' }}) || contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
with:
|
||||
name: ZedEditorUserSetup-x64-${{ github.event.pull_request.head.sha || github.sha }}.exe
|
||||
path: ${{ env.SETUP_PATH }}
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
|
||||
@@ -1404,8 +1404,8 @@
|
||||
// 4. A box drawn around the following character
|
||||
// "hollow"
|
||||
//
|
||||
// Default: "block"
|
||||
"cursor_shape": "block",
|
||||
// Default: not set, defaults to "block"
|
||||
"cursor_shape": null,
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
|
||||
@@ -1046,33 +1046,32 @@ impl AcpThreadView {
|
||||
};
|
||||
|
||||
let connection = thread.read(cx).connection().clone();
|
||||
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
|
||||
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
||||
let logout_supported = text == "/logout"
|
||||
&& self
|
||||
.available_commands
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|command| command.name == "logout");
|
||||
if can_login && !logout_supported {
|
||||
let this = cx.weak_entity();
|
||||
let agent = self.agent.clone();
|
||||
window.defer(cx, |window, cx| {
|
||||
Self::handle_auth_required(
|
||||
this,
|
||||
AuthRequired {
|
||||
description: None,
|
||||
provider_id: None,
|
||||
},
|
||||
agent,
|
||||
connection,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.notify();
|
||||
let auth_methods = connection.auth_methods();
|
||||
let has_supported_auth = auth_methods.iter().any(|method| {
|
||||
let id = method.id.0.as_ref();
|
||||
id == "claude-login" || id == "spawn-gemini-cli"
|
||||
});
|
||||
let can_login = has_supported_auth || auth_methods.is_empty() || self.login.is_some();
|
||||
if !can_login {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let this = cx.weak_entity();
|
||||
let agent = self.agent.clone();
|
||||
window.defer(cx, |window, cx| {
|
||||
Self::handle_auth_required(
|
||||
this,
|
||||
AuthRequired {
|
||||
description: None,
|
||||
provider_id: None,
|
||||
},
|
||||
agent,
|
||||
connection,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_impl(self.message_editor.clone(), window, cx)
|
||||
|
||||
@@ -16518,7 +16518,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
||||
leader.update(cx, |leader, cx| {
|
||||
leader.buffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
|
||||
PathKey::namespaced(1, rel_path("b.txt").into_arc()),
|
||||
buffer_1.clone(),
|
||||
vec![
|
||||
Point::row_range(0..3),
|
||||
@@ -16529,7 +16529,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
);
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
|
||||
PathKey::namespaced(1, rel_path("a.txt").into_arc()),
|
||||
buffer_2.clone(),
|
||||
vec![Point::row_range(0..6), Point::row_range(8..12)],
|
||||
0,
|
||||
@@ -21032,7 +21032,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
||||
for buffer in &buffers {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
|
||||
PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
|
||||
buffer.clone(),
|
||||
vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
|
||||
2,
|
||||
|
||||
@@ -43,8 +43,8 @@ struct CommitMetadataFile {
|
||||
worktree_id: WorktreeId,
|
||||
}
|
||||
|
||||
const COMMIT_METADATA_SORT_PREFIX: u64 = 0;
|
||||
const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
|
||||
const COMMIT_METADATA_NAMESPACE: u64 = 0;
|
||||
const FILE_NAMESPACE: u64 = 1;
|
||||
|
||||
impl CommitView {
|
||||
pub fn open(
|
||||
@@ -145,7 +145,7 @@ impl CommitView {
|
||||
});
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(COMMIT_METADATA_SORT_PREFIX, file.title.clone()),
|
||||
PathKey::namespaced(COMMIT_METADATA_NAMESPACE, file.title.clone()),
|
||||
buffer.clone(),
|
||||
vec![Point::zero()..buffer.read(cx).max_point()],
|
||||
0,
|
||||
@@ -193,7 +193,7 @@ impl CommitView {
|
||||
.collect::<Vec<_>>();
|
||||
let path = snapshot.file().unwrap().path().clone();
|
||||
let _is_newly_added = multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(FILE_NAMESPACE_SORT_PREFIX, path),
|
||||
PathKey::namespaced(FILE_NAMESPACE, path),
|
||||
buffer,
|
||||
diff_hunk_ranges,
|
||||
multibuffer_context_lines(cx),
|
||||
|
||||
@@ -4973,7 +4973,6 @@ mod tests {
|
||||
use settings::SettingsStore;
|
||||
use theme::LoadThemes;
|
||||
use util::path;
|
||||
use util::rel_path::rel_path;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -5596,68 +5595,6 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_open_diff(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
".git": {},
|
||||
"tracked": "tracked\n",
|
||||
"untracked": "\n",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.set_head_and_index_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("tracked", "old tracked\n".into())],
|
||||
);
|
||||
|
||||
let project = Project::test(fs.clone(), [Path::new(path!("/project"))], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let panel = workspace.update(cx, GitPanel::new).unwrap();
|
||||
|
||||
// Enable the `sort_by_path` setting and wait for entries to be updated,
|
||||
// as there should no longer be separators between Tracked and Untracked
|
||||
// files.
|
||||
cx.update(|_window, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.git_panel.get_or_insert_default().sort_by_path = Some(true);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
cx.update_window_entity(&panel, |panel, _, _| {
|
||||
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
|
||||
})
|
||||
.await;
|
||||
|
||||
// Confirm that `Open Diff` still works for the untracked file, updating
|
||||
// the Project Diff's active path.
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.selected_entry = Some(1);
|
||||
panel.open_diff(&Confirm, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let _ = workspace.update(cx, |workspace, _window, cx| {
|
||||
let active_path = workspace
|
||||
.item_of_type::<ProjectDiff>(cx)
|
||||
.expect("ProjectDiff should exist")
|
||||
.read(cx)
|
||||
.active_path(cx)
|
||||
.expect("active_path should exist");
|
||||
|
||||
assert_eq!(active_path.path, rel_path("untracked").into_arc());
|
||||
});
|
||||
}
|
||||
|
||||
fn assert_entry_paths(entries: &[GitListEntry], expected_paths: &[Option<&str>]) {
|
||||
assert_eq!(entries.len(), expected_paths.len());
|
||||
for (entry, expected_path) in entries.iter().zip(expected_paths) {
|
||||
|
||||
@@ -16,7 +16,7 @@ use editor::{
|
||||
use futures::StreamExt;
|
||||
use git::{
|
||||
Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
||||
repository::{Branch, RepoPath, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||
status::FileStatus,
|
||||
};
|
||||
use gpui::{
|
||||
@@ -27,7 +27,7 @@ use language::{Anchor, Buffer, Capability, OffsetRangeExt};
|
||||
use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::{
|
||||
Project, ProjectPath,
|
||||
git_store::{GitStore, GitStoreEvent, Repository},
|
||||
git_store::{GitStore, GitStoreEvent},
|
||||
};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::any::{Any, TypeId};
|
||||
@@ -73,9 +73,9 @@ struct DiffBuffer {
|
||||
file_status: FileStatus,
|
||||
}
|
||||
|
||||
const CONFLICT_SORT_PREFIX: u64 = 1;
|
||||
const TRACKED_SORT_PREFIX: u64 = 2;
|
||||
const NEW_SORT_PREFIX: u64 = 3;
|
||||
const CONFLICT_NAMESPACE: u64 = 1;
|
||||
const TRACKED_NAMESPACE: u64 = 2;
|
||||
const NEW_NAMESPACE: u64 = 3;
|
||||
|
||||
impl ProjectDiff {
|
||||
pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
|
||||
@@ -234,8 +234,16 @@ impl ProjectDiff {
|
||||
return;
|
||||
};
|
||||
let repo = git_repo.read(cx);
|
||||
let sort_prefix = sort_prefix(repo, &entry.repo_path, entry.status, cx);
|
||||
let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.0);
|
||||
|
||||
let namespace = if repo.had_conflict_on_last_merge_head_change(&entry.repo_path) {
|
||||
CONFLICT_NAMESPACE
|
||||
} else if entry.status.is_created() {
|
||||
NEW_NAMESPACE
|
||||
} else {
|
||||
TRACKED_NAMESPACE
|
||||
};
|
||||
|
||||
let path_key = PathKey::namespaced(namespace, entry.repo_path.0);
|
||||
|
||||
self.move_to_path(path_key, window, cx)
|
||||
}
|
||||
@@ -380,8 +388,16 @@ impl ProjectDiff {
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let sort_prefix = sort_prefix(repo, &entry.repo_path, entry.status, cx);
|
||||
let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.0.clone());
|
||||
let namespace = if GitPanelSettings::get_global(cx).sort_by_path {
|
||||
TRACKED_NAMESPACE
|
||||
} else if repo.had_conflict_on_last_merge_head_change(&entry.repo_path) {
|
||||
CONFLICT_NAMESPACE
|
||||
} else if entry.status.is_created() {
|
||||
NEW_NAMESPACE
|
||||
} else {
|
||||
TRACKED_NAMESPACE
|
||||
};
|
||||
let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone());
|
||||
|
||||
previous_paths.remove(&path_key);
|
||||
let load_buffer = self
|
||||
@@ -525,18 +541,6 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_prefix(repo: &Repository, repo_path: &RepoPath, status: FileStatus, cx: &App) -> u64 {
|
||||
if GitPanelSettings::get_global(cx).sort_by_path {
|
||||
TRACKED_SORT_PREFIX
|
||||
} else if repo.had_conflict_on_last_merge_head_change(repo_path) {
|
||||
CONFLICT_SORT_PREFIX
|
||||
} else if status.is_created() {
|
||||
NEW_SORT_PREFIX
|
||||
} else {
|
||||
TRACKED_SORT_PREFIX
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ProjectDiff {}
|
||||
|
||||
impl Focusable for ProjectDiff {
|
||||
@@ -1459,7 +1463,7 @@ mod tests {
|
||||
|
||||
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
|
||||
diff.move_to_path(
|
||||
PathKey::with_sort_prefix(TRACKED_SORT_PREFIX, rel_path("foo").into_arc()),
|
||||
PathKey::namespaced(TRACKED_NAMESPACE, rel_path("foo").into_arc()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -1480,7 +1484,7 @@ mod tests {
|
||||
|
||||
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
|
||||
diff.move_to_path(
|
||||
PathKey::with_sort_prefix(TRACKED_SORT_PREFIX, rel_path("bar").into_arc()),
|
||||
PathKey::namespaced(TRACKED_NAMESPACE, rel_path("bar").into_arc()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::time::Duration;
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
Animation, AnimationExt as _, App, Application, AssetSource, Bounds, Context, SharedString,
|
||||
Transformation, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage,
|
||||
prelude::*, px, size, svg,
|
||||
Transformation, Window, WindowBounds, WindowOptions, black, bounce, div, ease_in_out,
|
||||
percentage, prelude::*, px, rgb, size, svg,
|
||||
};
|
||||
|
||||
struct Assets {}
|
||||
@@ -37,66 +37,37 @@ struct AnimationExample {}
|
||||
|
||||
impl Render for AnimationExample {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.bg(gpui::white())
|
||||
.text_color(gpui::black())
|
||||
.justify_around()
|
||||
.child(
|
||||
div().flex().flex_col().size_full().justify_around().child(
|
||||
div().flex().flex_row().w_full().justify_around().child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.justify_around()
|
||||
.bg(rgb(0x2e7d32))
|
||||
.size(px(300.0))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
.text_xl()
|
||||
.text_color(black())
|
||||
.child("hello")
|
||||
.child(
|
||||
div()
|
||||
.id("content")
|
||||
.flex()
|
||||
.flex_col()
|
||||
.h(px(150.))
|
||||
.overflow_y_scroll()
|
||||
.w_full()
|
||||
.flex_1()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.text_xl()
|
||||
.gap_4()
|
||||
.child("Hello Animation")
|
||||
.child(
|
||||
svg()
|
||||
.size_20()
|
||||
.overflow_hidden()
|
||||
.path(ARROW_CIRCLE_SVG)
|
||||
.text_color(gpui::black())
|
||||
.with_animation(
|
||||
"image_circle",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(bounce(ease_in_out)),
|
||||
|svg, delta| {
|
||||
svg.with_transformation(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
),
|
||||
svg()
|
||||
.size_8()
|
||||
.path(ARROW_CIRCLE_SVG)
|
||||
.text_color(black())
|
||||
.with_animation(
|
||||
"image_circle",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(bounce(ease_in_out)),
|
||||
|svg, delta| {
|
||||
svg.with_transformation(Transformation::rotate(percentage(
|
||||
delta,
|
||||
)))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.h(px(64.))
|
||||
.w_full()
|
||||
.p_2()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.border_t_1()
|
||||
.border_color(gpui::black().opacity(0.1))
|
||||
.bg(gpui::black().opacity(0.05))
|
||||
.child("Other Panel"),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,12 +172,6 @@ fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds:
|
||||
return distance_from_clip_rect_impl(position, clip_bounds);
|
||||
}
|
||||
|
||||
fn distance_from_clip_rect_transformed(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds, transform: TransformationMatrix) -> vec4<f32> {
|
||||
let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
|
||||
let transformed = transpose(transform.rotation_scale) * position + transform.translation;
|
||||
return distance_from_clip_rect_impl(transformed, clip_bounds);
|
||||
}
|
||||
|
||||
// https://gamedev.stackexchange.com/questions/92015/optimized-linear-to-srgb-glsl
|
||||
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
|
||||
let cutoff = srgb < vec3<f32>(0.04045);
|
||||
@@ -1156,7 +1150,7 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
|
||||
|
||||
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
out.color = hsla_to_rgba(sprite.color);
|
||||
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size);
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds);
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds, TransformationMatrix transformation);
|
||||
float corner_dash_velocity(float dv1, float dv2);
|
||||
float dash_alpha(float t, float period, float length, float dash_velocity,
|
||||
float antialias_threshold);
|
||||
@@ -601,14 +599,13 @@ struct MonochromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
float4 clip_distance;
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct MonochromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
float4 clip_distance;
|
||||
};
|
||||
|
||||
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
@@ -623,8 +620,8 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
MonochromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds, sprite.transformation);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return MonochromeSpriteVertexOutput{
|
||||
@@ -638,10 +635,6 @@ fragment float4 monochrome_sprite_fragment(
|
||||
MonochromeSpriteFragmentInput input [[stage_in]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
if (any(input.clip_distance < float4(0.0))) {
|
||||
return float4(0.0);
|
||||
}
|
||||
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
@@ -1103,23 +1096,6 @@ float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - position.y);
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds, TransformationMatrix transformation) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
float2 transformed_position = float2(0, 0);
|
||||
transformed_position[0] = position[0] * transformation.rotation_scale[0][0] + position[1] * transformation.rotation_scale[0][1];
|
||||
transformed_position[1] = position[0] * transformation.rotation_scale[1][0] + position[1] * transformation.rotation_scale[1][1];
|
||||
transformed_position[0] += transformation.translation[0];
|
||||
transformed_position[1] += transformation.translation[1];
|
||||
|
||||
return float4(transformed_position.x - clip_bounds.origin.x,
|
||||
clip_bounds.origin.x + clip_bounds.size.width - transformed_position.x,
|
||||
transformed_position.y - clip_bounds.origin.y,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - transformed_position.y);
|
||||
}
|
||||
|
||||
float4 over(float4 below, float4 above) {
|
||||
float4 result;
|
||||
float alpha = above.a + below.a * (1.0 - above.a);
|
||||
|
||||
@@ -107,12 +107,6 @@ float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bo
|
||||
return distance_from_clip_rect_impl(position, clip_bounds);
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds bounds, Bounds clip_bounds, TransformationMatrix transformation) {
|
||||
float2 position = unit_vertex * bounds.size + bounds.origin;
|
||||
float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation;
|
||||
return distance_from_clip_rect_impl(transformed, clip_bounds);
|
||||
}
|
||||
|
||||
// Convert linear RGB to sRGB
|
||||
float3 linear_to_srgb(float3 color) {
|
||||
return pow(color, float3(2.2, 2.2, 2.2));
|
||||
@@ -1094,7 +1088,7 @@ MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexI
|
||||
MonochromeSprite sprite = mono_sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
|
||||
float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
|
||||
|
||||
@@ -54,10 +54,7 @@ impl SvgRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render(
|
||||
&self,
|
||||
params: &RenderSvgParams,
|
||||
) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
|
||||
pub(crate) fn render(&self, params: &RenderSvgParams) -> Result<Option<Vec<u8>>> {
|
||||
anyhow::ensure!(!params.size.is_zero(), "can't render at a zero size");
|
||||
|
||||
// Load the tree.
|
||||
@@ -68,33 +65,30 @@ impl SvgRenderer {
|
||||
let pixmap = self.render_pixmap(&bytes, SvgSize::Size(params.size))?;
|
||||
|
||||
// Convert the pixmap's pixels into an alpha mask.
|
||||
let size = Size::new(
|
||||
DevicePixels(pixmap.width() as i32),
|
||||
DevicePixels(pixmap.height() as i32),
|
||||
);
|
||||
let alpha_mask = pixmap
|
||||
.pixels()
|
||||
.iter()
|
||||
.map(|p| p.alpha())
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Some((size, alpha_mask)))
|
||||
Ok(Some(alpha_mask))
|
||||
}
|
||||
|
||||
pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
|
||||
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
|
||||
let svg_size = tree.size();
|
||||
let scale = match size {
|
||||
SvgSize::Size(size) => size.width.0 as f32 / svg_size.width(),
|
||||
SvgSize::ScaleFactor(scale) => scale,
|
||||
|
||||
let size = match size {
|
||||
SvgSize::Size(size) => size,
|
||||
SvgSize::ScaleFactor(scale) => crate::size(
|
||||
DevicePixels((tree.size().width() * scale) as i32),
|
||||
DevicePixels((tree.size().height() * scale) as i32),
|
||||
),
|
||||
};
|
||||
|
||||
// Render the SVG to a pixmap with the specified width and height.
|
||||
let mut pixmap = resvg::tiny_skia::Pixmap::new(
|
||||
(svg_size.width() * scale) as u32,
|
||||
(svg_size.height() * scale) as u32,
|
||||
)
|
||||
.ok_or(usvg::Error::InvalidSize)?;
|
||||
let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width.into(), size.height.into())
|
||||
.ok_or(usvg::Error::InvalidSize)?;
|
||||
|
||||
let scale = size.width.0 as f32 / tree.size().width();
|
||||
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
|
||||
|
||||
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
||||
|
||||
@@ -3082,31 +3082,22 @@ impl Window {
|
||||
let Some(tile) =
|
||||
self.sprite_atlas
|
||||
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||
let Some((size, bytes)) = cx.svg_renderer.render(¶ms)? else {
|
||||
let Some(bytes) = cx.svg_renderer.render(¶ms)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some((size, Cow::Owned(bytes))))
|
||||
Ok(Some((params.size, Cow::Owned(bytes))))
|
||||
})?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
let svg_bounds = Bounds {
|
||||
origin: bounds.center()
|
||||
- Point::new(
|
||||
ScaledPixels(tile.bounds.size.width.0 as f32 / SMOOTH_SVG_SCALE_FACTOR / 2.),
|
||||
ScaledPixels(tile.bounds.size.height.0 as f32 / SMOOTH_SVG_SCALE_FACTOR / 2.),
|
||||
),
|
||||
size: tile
|
||||
.bounds
|
||||
.size
|
||||
.map(|value| ScaledPixels(value.0 as f32 / SMOOTH_SVG_SCALE_FACTOR)),
|
||||
};
|
||||
|
||||
self.next_frame.scene.insert_primitive(MonochromeSprite {
|
||||
order: 0,
|
||||
pad: 0,
|
||||
bounds: svg_bounds,
|
||||
bounds: bounds
|
||||
.map_origin(|origin| origin.floor())
|
||||
.map_size(|size| size.ceil()),
|
||||
content_mask,
|
||||
color: color.opacity(element_opacity),
|
||||
tile,
|
||||
|
||||
@@ -161,25 +161,24 @@ impl MultiBufferDiffHunk {
|
||||
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
|
||||
pub struct PathKey {
|
||||
// Used by the derived PartialOrd & Ord
|
||||
sort_prefix: Option<u64>,
|
||||
namespace: Option<u64>,
|
||||
path: Arc<RelPath>,
|
||||
}
|
||||
|
||||
impl PathKey {
|
||||
pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
|
||||
pub fn namespaced(namespace: u64, path: Arc<RelPath>) -> Self {
|
||||
Self {
|
||||
sort_prefix: Some(sort_prefix),
|
||||
namespace: Some(namespace),
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
|
||||
if let Some(file) = buffer.read(cx).file() {
|
||||
Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
|
||||
Self::namespaced(file.worktree_id(cx).to_proto(), file.path().clone())
|
||||
} else {
|
||||
Self {
|
||||
sort_prefix: None,
|
||||
namespace: None,
|
||||
path: RelPath::unix(&buffer.entity_id().to_string())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
|
||||
@@ -1525,7 +1525,7 @@ fn test_set_excerpts_for_buffer_ordering(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
|
||||
let path1: PathKey = PathKey::namespaced(0, rel_path("root").into_arc());
|
||||
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
@@ -1620,7 +1620,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let path1: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
|
||||
let path1: PathKey = PathKey::namespaced(0, rel_path("root").into_arc());
|
||||
let buf2 = cx.new(|cx| {
|
||||
Buffer::local(
|
||||
indoc! {
|
||||
@@ -1639,7 +1639,7 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let path2 = PathKey::with_sort_prefix(1, rel_path("root").into_arc());
|
||||
let path2 = PathKey::namespaced(1, rel_path("root").into_arc());
|
||||
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
@@ -1816,7 +1816,7 @@ fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let path: PathKey = PathKey::with_sort_prefix(0, rel_path("root").into_arc());
|
||||
let path: PathKey = PathKey::namespaced(0, rel_path("root").into_arc());
|
||||
let buf2 = cx.new(|cx| {
|
||||
Buffer::local(
|
||||
indoc! {
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use client::Client;
|
||||
use collections::HashMap;
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
use fs::{Fs, RemoveOptions, RenameOptions};
|
||||
@@ -15,7 +16,7 @@ use futures::StreamExt as _;
|
||||
use gpui::{
|
||||
AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
|
||||
};
|
||||
use http_client::{HttpClient, github::AssetKind};
|
||||
use http_client::github::AssetKind;
|
||||
use node_runtime::NodeRuntime;
|
||||
use remote::RemoteClient;
|
||||
use rpc::{AnyProtoClient, TypedEnvelope, proto};
|
||||
@@ -113,7 +114,6 @@ enum AgentServerStoreState {
|
||||
project_environment: Entity<ProjectEnvironment>,
|
||||
downstream_client: Option<(u64, AnyProtoClient)>,
|
||||
settings: Option<AllAgentServersSettings>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
_subscriptions: [Subscription; 1],
|
||||
},
|
||||
Remote {
|
||||
@@ -174,7 +174,6 @@ impl AgentServerStore {
|
||||
project_environment,
|
||||
downstream_client,
|
||||
settings: old_settings,
|
||||
http_client,
|
||||
..
|
||||
} = &mut self.state
|
||||
else {
|
||||
@@ -228,8 +227,6 @@ impl AgentServerStore {
|
||||
.codex
|
||||
.clone()
|
||||
.and_then(|settings| settings.custom_command()),
|
||||
http_client: http_client.clone(),
|
||||
is_remote: downstream_client.is_some(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -256,6 +253,7 @@ impl AgentServerStore {
|
||||
names: self
|
||||
.external_agents
|
||||
.keys()
|
||||
.filter(|name| name.0 != CODEX_NAME)
|
||||
.map(|name| name.to_string())
|
||||
.collect(),
|
||||
})
|
||||
@@ -268,7 +266,6 @@ impl AgentServerStore {
|
||||
node_runtime: NodeRuntime,
|
||||
fs: Arc<dyn Fs>,
|
||||
project_environment: Entity<ProjectEnvironment>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let subscription = cx.observe_global::<SettingsStore>(|this, cx| {
|
||||
@@ -286,7 +283,6 @@ impl AgentServerStore {
|
||||
node_runtime,
|
||||
fs,
|
||||
project_environment,
|
||||
http_client,
|
||||
downstream_client: None,
|
||||
settings: None,
|
||||
_subscriptions: [subscription],
|
||||
@@ -301,12 +297,12 @@ impl AgentServerStore {
|
||||
pub(crate) fn remote(
|
||||
project_id: u64,
|
||||
upstream_client: Entity<RemoteClient>,
|
||||
cx: &mut Context<Self>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// Set up the builtin agents here so they're immediately available in
|
||||
// remote projects--we know that the HeadlessProject on the other end
|
||||
// will have them.
|
||||
let mut external_agents = [
|
||||
let external_agents = [
|
||||
(
|
||||
GEMINI_NAME.into(),
|
||||
Box::new(RemoteExternalAgentServer {
|
||||
@@ -329,21 +325,7 @@ impl AgentServerStore {
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<ExternalAgentServerName, Box<dyn ExternalAgentServer>>>();
|
||||
|
||||
use feature_flags::FeatureFlagAppExt as _;
|
||||
if cx.has_flag::<feature_flags::CodexAcpFeatureFlag>() {
|
||||
external_agents.insert(
|
||||
CODEX_NAME.into(),
|
||||
Box::new(RemoteExternalAgentServer {
|
||||
project_id,
|
||||
upstream_client: upstream_client.clone(),
|
||||
name: CODEX_NAME.into(),
|
||||
status_tx: None,
|
||||
new_version_available_tx: None,
|
||||
}) as Box<dyn ExternalAgentServer>,
|
||||
);
|
||||
}
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
state: AgentServerStoreState::Remote {
|
||||
@@ -1021,9 +1003,7 @@ impl ExternalAgentServer for LocalClaudeCode {
|
||||
struct LocalCodex {
|
||||
fs: Arc<dyn Fs>,
|
||||
project_environment: Entity<ProjectEnvironment>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
custom_command: Option<AgentServerCommand>,
|
||||
is_remote: bool,
|
||||
}
|
||||
|
||||
impl ExternalAgentServer for LocalCodex {
|
||||
@@ -1037,13 +1017,11 @@ impl ExternalAgentServer for LocalCodex {
|
||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
||||
let fs = self.fs.clone();
|
||||
let project_environment = self.project_environment.downgrade();
|
||||
let http = self.http_client.clone();
|
||||
let custom_command = self.custom_command.clone();
|
||||
let root_dir: Arc<Path> = root_dir
|
||||
.map(|root_dir| Path::new(root_dir))
|
||||
.unwrap_or(paths::home_dir())
|
||||
.into();
|
||||
let is_remote = self.is_remote;
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut env = project_environment
|
||||
@@ -1052,9 +1030,6 @@ impl ExternalAgentServer for LocalCodex {
|
||||
})?
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if is_remote {
|
||||
env.insert("NO_BROWSER".to_owned(), "1".to_owned());
|
||||
}
|
||||
|
||||
let mut command = if let Some(mut custom_command) = custom_command {
|
||||
env.extend(custom_command.env.unwrap_or_default());
|
||||
@@ -1065,6 +1040,7 @@ impl ExternalAgentServer for LocalCodex {
|
||||
fs.create_dir(&dir).await?;
|
||||
|
||||
// Find or install the latest Codex release (no update checks for now).
|
||||
let http = cx.update(|cx| Client::global(cx).http_client())?;
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
CODEX_ACP_REPO,
|
||||
true,
|
||||
|
||||
@@ -1155,13 +1155,7 @@ impl Project {
|
||||
});
|
||||
|
||||
let agent_server_store = cx.new(|cx| {
|
||||
AgentServerStore::local(
|
||||
node.clone(),
|
||||
fs.clone(),
|
||||
environment.clone(),
|
||||
client.http_client(),
|
||||
cx,
|
||||
)
|
||||
AgentServerStore::local(node.clone(), fs.clone(), environment.clone(), cx)
|
||||
});
|
||||
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
|
||||
@@ -244,7 +244,7 @@ impl Project {
|
||||
task_state,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
@@ -374,7 +374,7 @@ impl Project {
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
|
||||
@@ -196,13 +196,8 @@ impl HeadlessProject {
|
||||
});
|
||||
|
||||
let agent_server_store = cx.new(|cx| {
|
||||
let mut agent_server_store = AgentServerStore::local(
|
||||
node_runtime.clone(),
|
||||
fs.clone(),
|
||||
environment,
|
||||
http_client.clone(),
|
||||
cx,
|
||||
);
|
||||
let mut agent_server_store =
|
||||
AgentServerStore::local(node_runtime.clone(), fs.clone(), environment, cx);
|
||||
agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
|
||||
agent_server_store
|
||||
});
|
||||
|
||||
@@ -1792,7 +1792,7 @@ async fn test_remote_external_agent_server(
|
||||
.map(|name| name.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
pretty_assertions::assert_eq!(names, ["gemini", "codex", "claude"]);
|
||||
pretty_assertions::assert_eq!(names, ["gemini", "claude"]);
|
||||
server_cx.update_global::<SettingsStore, _>(|settings_store, cx| {
|
||||
settings_store
|
||||
.set_server_settings(
|
||||
@@ -1822,7 +1822,7 @@ async fn test_remote_external_agent_server(
|
||||
.map(|name| name.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
pretty_assertions::assert_eq!(names, ["gemini", "codex", "claude", "foo"]);
|
||||
pretty_assertions::assert_eq!(names, ["gemini", "foo", "claude"]);
|
||||
let (command, root, login) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.agent_server_store().update(cx, |store, cx| {
|
||||
|
||||
@@ -65,7 +65,7 @@ pub struct TerminalSettingsContent {
|
||||
/// Default cursor shape for the terminal.
|
||||
/// Can be "bar", "block", "underline", or "hollow".
|
||||
///
|
||||
/// Default: "block"
|
||||
/// Default: None
|
||||
pub cursor_shape: Option<CursorShapeContent>,
|
||||
/// Sets the cursor blinking behavior in the terminal.
|
||||
///
|
||||
@@ -236,18 +236,7 @@ pub enum ShowScrollbar {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
// todo() -> combine with CursorShape
|
||||
@@ -263,19 +252,7 @@ pub enum CursorShapeContent {
|
||||
Hollow,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TerminalBlink {
|
||||
/// Never blink the cursor, ignoring the terminal mode.
|
||||
@@ -287,19 +264,7 @@ pub enum TerminalBlink {
|
||||
On,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::VariantArray,
|
||||
strum::VariantNames,
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlternateScroll {
|
||||
On,
|
||||
|
||||
@@ -3916,519 +3916,6 @@ pub(crate) fn settings_data() -> Vec<SettingsPage> {
|
||||
}),
|
||||
],
|
||||
},
|
||||
SettingsPage {
|
||||
title: "Terminal",
|
||||
items: vec![
|
||||
SettingsPageItem::SectionHeader("Environment"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Shell",
|
||||
description: "What shell to use when opening a terminal",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.project.shell
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Working Directory",
|
||||
description: "What working directory to use when launching the terminal",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.project.working_directory
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.working_directory
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Environment Variables",
|
||||
description: "Key-value pairs to add to the terminal's environment",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.project.env
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.env
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Detect Virtual Environment",
|
||||
description: "Activates the python virtual environment, if one is found, in the terminal's working directory",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.project.detect_venv
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.detect_venv
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Font"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Size",
|
||||
description: "Font size for terminal text. If not set, defaults to buffer font size",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.font_size
|
||||
} else if settings_content.theme.buffer_font_size.is_some() {
|
||||
&settings_content.theme.buffer_font_size
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.terminal.get_or_insert_default().font_size
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Family",
|
||||
description: "Font family for terminal text. If not set, defaults to buffer font family",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal
|
||||
&& terminal.font_family.is_some()
|
||||
{
|
||||
&terminal.font_family
|
||||
} else if settings_content.theme.buffer_font_family.is_some() {
|
||||
&settings_content.theme.buffer_font_family
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.font_family
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Fallbacks",
|
||||
description: "Font fallbacks for terminal text. If not set, defaults to buffer font fallbacks",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.font_fallbacks
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.font_fallbacks
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Weight",
|
||||
description: "Font weight for terminal text in CSS weight units (100-900)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.font_weight
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.font_weight
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Font Features",
|
||||
description: "Font features for terminal text",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.font_features
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.font_features
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Display Settings"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Line Height",
|
||||
description: "Line height for terminal text",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.line_height
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.line_height
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Cursor Shape",
|
||||
description: "Default cursor shape for the terminal (bar, block, underline, or hollow)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.cursor_shape
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.cursor_shape
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Cursor Blinking",
|
||||
description: "Sets the cursor blinking behavior in the terminal",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.blinking
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.terminal.get_or_insert_default().blinking
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Alternate Scroll",
|
||||
description: "Whether Alternate Scroll mode is active by default (converts mouse scroll to arrow keys in apps like vim)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.alternate_scroll
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.alternate_scroll
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Minimum Contrast",
|
||||
description: "The minimum APCA perceptual contrast between foreground and background colors (0-106)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.minimum_contrast
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.minimum_contrast
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Behavior Settings"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Option As Meta",
|
||||
description: "Whether the option key behaves as the meta key",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.option_as_meta
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.option_as_meta
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Copy On Select",
|
||||
description: "Whether selecting text in the terminal automatically copies to the system clipboard",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.copy_on_select
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.copy_on_select
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Keep Selection On Copy",
|
||||
description: "Whether to keep the text selection after copying it to the clipboard",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.keep_selection_on_copy
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.keep_selection_on_copy
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Layout Settings"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Default Width",
|
||||
description: "Default width when the terminal is docked to the left or right (in pixels)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.default_width
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.default_width
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Default Height",
|
||||
description: "Default height when the terminal is docked to the bottom (in pixels)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.default_height
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.default_height
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Advanced Settings"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Max Scroll History Lines",
|
||||
description: "Maximum number of lines to keep in scrollback history (max: 100,000; 0 disables scrolling)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
&terminal.max_scroll_history_lines
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.max_scroll_history_lines
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Toolbar"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Breadcrumbs",
|
||||
description: "Whether to display the terminal title in breadcrumbs inside the terminal pane",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal {
|
||||
if let Some(toolbar) = &terminal.toolbar {
|
||||
&toolbar.breadcrumbs
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.toolbar
|
||||
.get_or_insert_default()
|
||||
.breadcrumbs
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Scrollbar"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Show Scrollbar",
|
||||
description: "When to show the scrollbar in the terminal",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
if let Some(terminal) = &settings_content.terminal
|
||||
&& let Some(scrollbar) = &terminal.scrollbar
|
||||
&& scrollbar.show.is_some()
|
||||
{
|
||||
&scrollbar.show
|
||||
} else if let Some(scrollbar) = &settings_content.editor.scrollbar {
|
||||
&scrollbar.show
|
||||
} else {
|
||||
&None
|
||||
}
|
||||
},
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.scrollbar
|
||||
.get_or_insert_default()
|
||||
.show
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
}),
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5279,7 +4766,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "LSP",
|
||||
title: "Lsp",
|
||||
description: "Whether to fetch LSP completions or not",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -5301,7 +4788,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "LSP Fetch Timeout Ms",
|
||||
title: "Lsp Fetch Timeout Ms",
|
||||
description: "When fetching LSP completions, determines how long to wait for a response of a particular server (set to 0 to wait indefinitely)",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -5326,7 +4813,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "LSP Insert Mode",
|
||||
title: "Lsp Insert Mode",
|
||||
description: "Controls how LSP completions are inserted",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
@@ -5616,7 +5103,7 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
|
||||
files: USER | LOCAL,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Prefer LSP",
|
||||
title: "Prefer Lsp",
|
||||
description: "Use LSP tasks over Zed language extension ones",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
|
||||
@@ -200,19 +200,10 @@ impl SettingFieldRenderer {
|
||||
if let Some(renderer) = self.renderers.borrow().get(&key) {
|
||||
renderer(any_setting_field, settings_file, metadata, window, cx)
|
||||
} else {
|
||||
Button::new("no-renderer", "NO RENDERER")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.icon(Some(IconName::XCircle))
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_color(Color::Error)
|
||||
.tab_index(0_isize)
|
||||
.tooltip(Tooltip::text(any_setting_field.type_name()))
|
||||
.into_any_element()
|
||||
// panic!(
|
||||
// "No renderer found for type: {}",
|
||||
// any_setting_field.type_name()
|
||||
// )
|
||||
panic!(
|
||||
"No renderer found for type: {}",
|
||||
any_setting_field.type_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,15 +409,6 @@ fn init_renderers(cx: &mut App) {
|
||||
.add_renderer::<settings::LspInsertMode>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::AlternateScroll>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::TerminalBlink>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<settings::CursorShapeContent>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<f32>(|settings_field, file, _, window, cx| {
|
||||
render_number_field(*settings_field, file, window, cx)
|
||||
})
|
||||
@@ -1729,7 +1711,6 @@ impl SettingsWindow {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.min_w_0()
|
||||
.id(("settings-page-item", actual_item_index))
|
||||
.when_some(page_index, |element, page_index| {
|
||||
element.track_focus(
|
||||
&self.content_handles[page_index][actual_item_index]
|
||||
|
||||
@@ -31,7 +31,7 @@ pub struct TerminalSettings {
|
||||
pub font_weight: Option<FontWeight>,
|
||||
pub line_height: TerminalLineHeight,
|
||||
pub env: HashMap<String, String>,
|
||||
pub cursor_shape: CursorShape,
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
pub blinking: TerminalBlink,
|
||||
pub alternate_scroll: AlternateScroll,
|
||||
pub option_as_meta: bool,
|
||||
@@ -95,7 +95,7 @@ impl settings::Settings for TerminalSettings {
|
||||
font_weight: user_content.font_weight.map(FontWeight),
|
||||
line_height: user_content.line_height.unwrap(),
|
||||
env: project_content.env.unwrap(),
|
||||
cursor_shape: user_content.cursor_shape.unwrap().into(),
|
||||
cursor_shape: user_content.cursor_shape.map(Into::into),
|
||||
blinking: user_content.blinking.unwrap(),
|
||||
alternate_scroll: user_content.alternate_scroll.unwrap(),
|
||||
option_as_meta: user_content.option_as_meta.unwrap(),
|
||||
|
||||
@@ -234,7 +234,9 @@ impl TerminalView {
|
||||
terminal_view.focus_out(window, cx);
|
||||
},
|
||||
);
|
||||
let cursor_shape = TerminalSettings::get_global(cx).cursor_shape;
|
||||
let cursor_shape = TerminalSettings::get_global(cx)
|
||||
.cursor_shape
|
||||
.unwrap_or_default();
|
||||
|
||||
let scroll_handle = TerminalScrollHandle::new(terminal.read(cx));
|
||||
|
||||
@@ -425,7 +427,7 @@ impl TerminalView {
|
||||
let breadcrumb_visibility_changed = self.show_breadcrumbs != settings.toolbar.breadcrumbs;
|
||||
self.show_breadcrumbs = settings.toolbar.breadcrumbs;
|
||||
|
||||
let new_cursor_shape = settings.cursor_shape;
|
||||
let new_cursor_shape = settings.cursor_shape.unwrap_or_default();
|
||||
let old_cursor_shape = self.cursor_shape;
|
||||
if old_cursor_shape != new_cursor_shape {
|
||||
self.cursor_shape = new_cursor_shape;
|
||||
|
||||
Reference in New Issue
Block a user