Compare commits
50 Commits
toggle_wit
...
git-panel-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3e04867ab | ||
|
|
3abf165c1d | ||
|
|
48eae645f2 | ||
|
|
78b6cae754 | ||
|
|
1cac4b6c00 | ||
|
|
ec741d61ed | ||
|
|
426f94b310 | ||
|
|
eff61ee764 | ||
|
|
aee641d79d | ||
|
|
caefdcd7f1 | ||
|
|
ff2ad63037 | ||
|
|
88f7942f11 | ||
|
|
188c55c8a6 | ||
|
|
2562b488b1 | ||
|
|
bc113e4b51 | ||
|
|
ea012075fc | ||
|
|
ce727fbc07 | ||
|
|
62b3acee5f | ||
|
|
38c0aa303e | ||
|
|
040d9ae222 | ||
|
|
d135ec2b73 | ||
|
|
a94afbc062 | ||
|
|
7b721efe2c | ||
|
|
18b6d142c3 | ||
|
|
53c9af3e61 | ||
|
|
af50261ae2 | ||
|
|
f64fcedabb | ||
|
|
7e6233d70f | ||
|
|
d459f010b6 | ||
|
|
25970650a7 | ||
|
|
9daa426e93 | ||
|
|
6e1cc5dad3 | ||
|
|
c5fe6ef100 | ||
|
|
1ac60289fe | ||
|
|
cbc226597c | ||
|
|
ff2d20780f | ||
|
|
cd5d8b4173 | ||
|
|
0be7cf8ea8 | ||
|
|
4f96706161 | ||
|
|
4f439ae35f | ||
|
|
85c3aec6e7 | ||
|
|
c2eea3a474 | ||
|
|
695f06c020 | ||
|
|
901dbedf8d | ||
|
|
99dc85e6ec | ||
|
|
735849e201 | ||
|
|
06edcd18be | ||
|
|
e53c1a8ee3 | ||
|
|
421974f923 | ||
|
|
c57cc35b03 |
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
if: github.repository == 'zed-industries/zed'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
if: github.repository == 'zed-industries/zed'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
|
||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -140,7 +140,7 @@ jobs:
|
||||
name: Create a Linux *.tar.gz bundle for ARM
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on:
|
||||
- hosted-linux-arm-1
|
||||
- buildjet-16vcpu-ubuntu-2204-arm
|
||||
needs: tests
|
||||
env:
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
|
||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -472,6 +472,8 @@ dependencies = [
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"handlebars 4.5.0",
|
||||
"html_to_markdown",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
@@ -5180,14 +5182,17 @@ dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"db",
|
||||
"editor",
|
||||
"git",
|
||||
"gpui",
|
||||
"language",
|
||||
"project",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"ctrl->": "zed::IncreaseBufferFontSize",
|
||||
"ctrl-<": "zed::DecreaseBufferFontSize",
|
||||
"ctrl-shift-j": "editor::JoinLines",
|
||||
"ctrl-d": "editor::DuplicateLineDown",
|
||||
"ctrl-d": "editor::DuplicateSelection",
|
||||
"ctrl-y": "editor::DeleteLine",
|
||||
"ctrl-m": "editor::ScrollCursorCenter",
|
||||
"ctrl-pagedown": "editor::MovePageDown",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
|
||||
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"ctrl-shift-d": "editor::DuplicateLineDown",
|
||||
"ctrl-shift-d": "editor::DuplicateSelection",
|
||||
"alt-f3": "editor::SelectAllMatches", // find_all_under
|
||||
"f12": "editor::GoToDefinition",
|
||||
"ctrl-f12": "editor::GoToDefinitionSplit",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"ctrl->": "zed::IncreaseBufferFontSize",
|
||||
"ctrl-<": "zed::DecreaseBufferFontSize",
|
||||
"ctrl-shift-j": "editor::JoinLines",
|
||||
"cmd-d": "editor::DuplicateLineDown",
|
||||
"cmd-d": "editor::DuplicateSelection",
|
||||
"cmd-backspace": "editor::DeleteLine",
|
||||
"cmd-pagedown": "editor::MovePageDown",
|
||||
"cmd-pageup": "editor::MovePageUp",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
||||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"cmd-shift-d": "editor::DuplicateLineDown",
|
||||
"cmd-shift-d": "editor::DuplicateSelection",
|
||||
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
|
||||
@@ -56,7 +56,7 @@ use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, text_for_action, ToggleWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
||||
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
||||
};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
@@ -2129,7 +2129,7 @@ impl PromptEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(ToggleWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"dont-show-again",
|
||||
Label::new("Don't show again"),
|
||||
if dismissed_rate_limit_notice() {
|
||||
|
||||
@@ -31,6 +31,8 @@ futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
handlebars.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
http_client.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
|
||||
@@ -23,4 +23,5 @@ pub struct Context {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
FetchedUrl,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod fetch_context_picker;
|
||||
mod file_context_picker;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -11,6 +12,7 @@ use ui::{prelude::*, ListItem, ListItemSpacing, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context_picker::fetch_context_picker::FetchContextPicker;
|
||||
use crate::context_picker::file_context_picker::FileContextPicker;
|
||||
use crate::message_editor::MessageEditor;
|
||||
|
||||
@@ -18,6 +20,7 @@ use crate::message_editor::MessageEditor;
|
||||
enum ContextPickerMode {
|
||||
Default,
|
||||
File(View<FileContextPicker>),
|
||||
Fetch(View<FetchContextPicker>),
|
||||
}
|
||||
|
||||
pub(super) struct ContextPicker {
|
||||
@@ -47,7 +50,7 @@ impl ContextPicker {
|
||||
icon: IconName::File,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "web".into(),
|
||||
name: "fetch".into(),
|
||||
description: "Fetch content from URL".into(),
|
||||
icon: IconName::Globe,
|
||||
},
|
||||
@@ -77,16 +80,21 @@ impl FocusableView for ContextPicker {
|
||||
match &self.mode {
|
||||
ContextPickerMode::Default => self.picker.focus_handle(cx),
|
||||
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
|
||||
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex().min_w(px(400.)).map(|parent| match &self.mode {
|
||||
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
||||
})
|
||||
v_flex()
|
||||
.w(px(400.))
|
||||
.min_w(px(400.))
|
||||
.map(|parent| match &self.mode {
|
||||
ContextPickerMode::Default => parent.child(self.picker.clone()),
|
||||
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
|
||||
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +152,16 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
)
|
||||
}));
|
||||
}
|
||||
"fetch" => {
|
||||
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
|
||||
FetchContextPicker::new(
|
||||
self.context_picker.clone(),
|
||||
self.workspace.clone(),
|
||||
self.message_editor.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -157,7 +175,7 @@ impl PickerDelegate for ContextPickerDelegate {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| match this.mode {
|
||||
ContextPickerMode::Default => cx.emit(DismissEvent),
|
||||
ContextPickerMode::File(_) => {}
|
||||
ContextPickerMode::File(_) | ContextPickerMode::Fetch(_) => {}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
218
crates/assistant2/src/context_picker/fetch_context_picker.rs
Normal file
218
crates/assistant2/src/context_picker/fetch_context_picker.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
|
||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, ViewContext};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::message_editor::MessageEditor;
|
||||
|
||||
pub struct FetchContextPicker {
|
||||
picker: View<Picker<FetchContextPickerDelegate>>,
|
||||
}
|
||||
|
||||
impl FetchContextPicker {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
message_editor: WeakView<MessageEditor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, message_editor);
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for FetchContextPicker {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FetchContextPicker {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
self.picker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum ContentType {
|
||||
Html,
|
||||
Plaintext,
|
||||
Json,
|
||||
}
|
||||
|
||||
pub struct FetchContextPickerDelegate {
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
message_editor: WeakView<MessageEditor>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl FetchContextPickerDelegate {
|
||||
pub fn new(
|
||||
context_picker: WeakView<ContextPicker>,
|
||||
workspace: WeakView<Workspace>,
|
||||
message_editor: WeakView<MessageEditor>,
|
||||
) -> Self {
|
||||
FetchContextPickerDelegate {
|
||||
context_picker,
|
||||
workspace,
|
||||
message_editor,
|
||||
url: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
|
||||
let mut url = url.to_owned();
|
||||
if !url.starts_with("https://") && !url.starts_with("http://") {
|
||||
url = format!("https://{url}");
|
||||
}
|
||||
|
||||
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut body)
|
||||
.await
|
||||
.context("error reading response body")?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let text = String::from_utf8_lossy(body.as_slice());
|
||||
bail!(
|
||||
"status error {}, response: {text:?}",
|
||||
response.status().as_u16()
|
||||
);
|
||||
}
|
||||
|
||||
let Some(content_type) = response.headers().get("content-type") else {
|
||||
bail!("missing Content-Type header");
|
||||
};
|
||||
let content_type = content_type
|
||||
.to_str()
|
||||
.context("invalid Content-Type header")?;
|
||||
let content_type = match content_type {
|
||||
"text/html" => ContentType::Html,
|
||||
"text/plain" => ContentType::Plaintext,
|
||||
"application/json" => ContentType::Json,
|
||||
_ => ContentType::Html,
|
||||
};
|
||||
|
||||
match content_type {
|
||||
ContentType::Html => {
|
||||
let mut handlers: Vec<TagHandler> = vec![
|
||||
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
|
||||
Rc::new(RefCell::new(markdown::ParagraphHandler)),
|
||||
Rc::new(RefCell::new(markdown::HeadingHandler)),
|
||||
Rc::new(RefCell::new(markdown::ListHandler)),
|
||||
Rc::new(RefCell::new(markdown::TableHandler::new())),
|
||||
Rc::new(RefCell::new(markdown::StyledTextHandler)),
|
||||
];
|
||||
if url.contains("wikipedia.org") {
|
||||
use html_to_markdown::structure::wikipedia;
|
||||
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
|
||||
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
|
||||
handlers.push(Rc::new(
|
||||
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
|
||||
));
|
||||
} else {
|
||||
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
|
||||
}
|
||||
|
||||
convert_html_to_markdown(&body[..], &mut handlers)
|
||||
}
|
||||
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
|
||||
ContentType::Json => {
|
||||
let json: serde_json::Value = serde_json::from_slice(&body)?;
|
||||
|
||||
Ok(format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(&json)?
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for FetchContextPickerDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut ui::WindowContext) -> Arc<str> {
|
||||
"Enter a URL…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
self.url = query;
|
||||
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let http_client = workspace.read(cx).client().http_client().clone();
|
||||
let url = self.url.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let text = Self::build_message(http_client, &url).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate
|
||||
.message_editor
|
||||
.update(cx, |message_editor, _cx| {
|
||||
message_editor.insert_context(ContextKind::FetchedUrl, url, text);
|
||||
})
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.context_picker
|
||||
.update(cx, |this, cx| {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(self.url.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
this.reset_mode();
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.log_err();
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
|
||||
@@ -53,7 +53,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ToggleWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip};
|
||||
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{dock::Panel, ShowConfiguration};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
@@ -2061,7 +2061,7 @@ impl PromptEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(ToggleWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"dont-show-again",
|
||||
Label::new("Don't show again"),
|
||||
if dismissed_rate_limit_notice() {
|
||||
|
||||
@@ -7,7 +7,7 @@ use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopove
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ToggleWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
|
||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
|
||||
PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
@@ -262,7 +262,7 @@ impl Render for MessageEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(h_flex().gap_2().child(ToggleWithLabel::new(
|
||||
.child(h_flex().gap_2().child(CheckboxWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
|
||||
@@ -193,12 +193,19 @@ impl Thread {
|
||||
|
||||
if let Some(context) = self.context_for_message(message.id) {
|
||||
let mut file_context = String::new();
|
||||
let mut fetch_context = String::new();
|
||||
|
||||
for context in context.iter() {
|
||||
match context.kind {
|
||||
ContextKind::File => {
|
||||
file_context.push_str(&context.text);
|
||||
file_context.push_str("\n");
|
||||
file_context.push('\n');
|
||||
}
|
||||
ContextKind::FetchedUrl => {
|
||||
fetch_context.push_str(&context.name);
|
||||
fetch_context.push('\n');
|
||||
fetch_context.push_str(&context.text);
|
||||
fetch_context.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,6 +216,11 @@ impl Thread {
|
||||
context_text.push_str(&file_context);
|
||||
}
|
||||
|
||||
if !fetch_context.is_empty() {
|
||||
context_text.push_str("The following fetched results are available\n");
|
||||
context_text.push_str(&fetch_context);
|
||||
}
|
||||
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::Text(context_text))
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, Avatar, ToggleWithLabel, ContextMenu, ListItem, ListItemSpacing};
|
||||
use ui::{prelude::*, Avatar, CheckboxWithLabel, ContextMenu, ListItem, ListItemSpacing};
|
||||
use util::TryFutureExt;
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView};
|
||||
|
||||
@@ -155,7 +155,7 @@ impl Render for ChannelModal {
|
||||
.h(rems_from_px(22.))
|
||||
.justify_between()
|
||||
.line_height(rems(1.25))
|
||||
.child(ToggleWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"is-public",
|
||||
Label::new("Public").size(LabelSize::Small),
|
||||
if visibility == ChannelVisibility::Public {
|
||||
|
||||
@@ -6135,29 +6135,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
for selection in selections.iter() {
|
||||
let start = selection.start;
|
||||
let end = selection.end;
|
||||
let text = buffer.text_for_range(start..end).collect::<String>();
|
||||
edits.push((selection.end..selection.end, text));
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
});
|
||||
|
||||
this.request_autoscroll(Autoscroll::fit(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn duplicate(&mut self, upwards: bool, whole_lines: bool, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
@@ -6165,36 +6143,44 @@ impl Editor {
|
||||
let mut edits = Vec::new();
|
||||
let mut selections_iter = selections.iter().peekable();
|
||||
while let Some(selection) = selections_iter.next() {
|
||||
// Avoid duplicating the same lines twice.
|
||||
let mut rows = selection.spanned_rows(false, &display_map);
|
||||
|
||||
while let Some(next_selection) = selections_iter.peek() {
|
||||
let next_rows = next_selection.spanned_rows(false, &display_map);
|
||||
if next_rows.start < rows.end {
|
||||
rows.end = next_rows.end;
|
||||
selections_iter.next().unwrap();
|
||||
} else {
|
||||
break;
|
||||
// duplicate line-wise
|
||||
if whole_lines || selection.start == selection.end {
|
||||
// Avoid duplicating the same lines twice.
|
||||
while let Some(next_selection) = selections_iter.peek() {
|
||||
let next_rows = next_selection.spanned_rows(false, &display_map);
|
||||
if next_rows.start < rows.end {
|
||||
rows.end = next_rows.end;
|
||||
selections_iter.next().unwrap();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the text from the selected row region and splice it either at the start
|
||||
// or end of the region.
|
||||
let start = Point::new(rows.start.0, 0);
|
||||
let end = Point::new(
|
||||
rows.end.previous_row().0,
|
||||
buffer.line_len(rows.end.previous_row()),
|
||||
);
|
||||
let text = buffer
|
||||
.text_for_range(start..end)
|
||||
.chain(Some("\n"))
|
||||
.collect::<String>();
|
||||
let insert_location = if upwards {
|
||||
Point::new(rows.end.0, 0)
|
||||
// Copy the text from the selected row region and splice it either at the start
|
||||
// or end of the region.
|
||||
let start = Point::new(rows.start.0, 0);
|
||||
let end = Point::new(
|
||||
rows.end.previous_row().0,
|
||||
buffer.line_len(rows.end.previous_row()),
|
||||
);
|
||||
let text = buffer
|
||||
.text_for_range(start..end)
|
||||
.chain(Some("\n"))
|
||||
.collect::<String>();
|
||||
let insert_location = if upwards {
|
||||
Point::new(rows.end.0, 0)
|
||||
} else {
|
||||
start
|
||||
};
|
||||
edits.push((insert_location..insert_location, text));
|
||||
} else {
|
||||
start
|
||||
};
|
||||
edits.push((insert_location..insert_location, text));
|
||||
// duplicate character-wise
|
||||
let start = selection.start;
|
||||
let end = selection.end;
|
||||
let text = buffer.text_for_range(start..end).collect::<String>();
|
||||
edits.push((selection.end..selection.end, text));
|
||||
}
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
@@ -6207,11 +6193,15 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
|
||||
self.duplicate_line(true, cx);
|
||||
self.duplicate(true, true, cx);
|
||||
}
|
||||
|
||||
pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
|
||||
self.duplicate_line(false, cx);
|
||||
self.duplicate(false, true, cx);
|
||||
}
|
||||
|
||||
pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
|
||||
self.duplicate(false, false, cx);
|
||||
}
|
||||
|
||||
pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {
|
||||
|
||||
@@ -5,7 +5,7 @@ use project::project_settings::{InlineBlameSettings, ProjectSettings};
|
||||
use settings::{EditableSettingControl, Settings};
|
||||
use theme::{FontFamilyCache, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, ToggleWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
|
||||
prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
|
||||
SettingsGroup,
|
||||
};
|
||||
|
||||
@@ -258,7 +258,7 @@ impl RenderOnce for BufferFontLigaturesControl {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let value = Self::read(cx);
|
||||
|
||||
ToggleWithLabel::new(
|
||||
CheckboxWithLabel::new(
|
||||
"buffer-font-ligatures",
|
||||
Label::new(self.name()),
|
||||
value.into(),
|
||||
@@ -311,7 +311,7 @@ impl RenderOnce for InlineGitBlameControl {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let value = Self::read(cx);
|
||||
|
||||
ToggleWithLabel::new(
|
||||
CheckboxWithLabel::new(
|
||||
"inline-git-blame",
|
||||
Label::new(self.name()),
|
||||
value.into(),
|
||||
@@ -364,7 +364,7 @@ impl RenderOnce for LineNumbersControl {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let value = Self::read(cx);
|
||||
|
||||
ToggleWithLabel::new(
|
||||
CheckboxWithLabel::new(
|
||||
"line-numbers",
|
||||
Label::new(self.name()),
|
||||
value.into(),
|
||||
|
||||
@@ -23,7 +23,7 @@ use project::DirectoryLister;
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ToggleWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
|
||||
use ui::{prelude::*, CheckboxWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::{
|
||||
item::{Item, ItemEvent},
|
||||
@@ -994,7 +994,7 @@ impl ExtensionsPage {
|
||||
.docs_url("https://zed.dev/docs/git#git-integrations"),
|
||||
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!")
|
||||
.docs_url("https://zed.dev/docs/vim")
|
||||
.child(ToggleWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"enable-vim",
|
||||
Label::new("Enable vim mode"),
|
||||
if VimModeSetting::get_global(cx).0 {
|
||||
|
||||
@@ -14,19 +14,22 @@ path = "src/git_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
git.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
git.workspace = true
|
||||
collections.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use anyhow::Context;
|
||||
use collections::HashMap;
|
||||
use editor::Editor;
|
||||
use language::Buffer;
|
||||
use std::{
|
||||
cell::OnceCell,
|
||||
collections::HashSet,
|
||||
@@ -8,6 +11,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use git::repository::GitFileStatus;
|
||||
|
||||
@@ -24,7 +28,7 @@ use ui::{
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{git_status_icon, settings::GitPanelSettings};
|
||||
use crate::{git_status_icon, settings::GitPanelSettings, GitState};
|
||||
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
|
||||
|
||||
actions!(git_panel, [ToggleFocus]);
|
||||
@@ -84,6 +88,8 @@ pub struct GitPanel {
|
||||
selected_item: Option<usize>,
|
||||
show_scrollbar: bool,
|
||||
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
|
||||
git_state: Model<GitState>,
|
||||
editor: View<Editor>,
|
||||
|
||||
// The entries that are currently shown in the panel, aka
|
||||
// not hidden by folding or such
|
||||
@@ -104,11 +110,17 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
let git_state = GitState::get_global(cx);
|
||||
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let weak_workspace = workspace.weak_handle();
|
||||
let project = workspace.project().clone();
|
||||
let language_registry = workspace.app_state().languages.clone();
|
||||
|
||||
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let state = git_state.read(cx);
|
||||
let current_commit_message = state.commit_message.clone();
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus(&focus_handle, Self::focus_in).detach();
|
||||
cx.on_focus_out(&focus_handle, |this, _, cx| {
|
||||
@@ -131,6 +143,55 @@ impl GitPanel {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let theme = ThemeSettings::get_global(cx);
|
||||
|
||||
let mut text_style = cx.text_style();
|
||||
let refinement = TextStyleRefinement {
|
||||
font_family: Some(theme.buffer_font.family.clone()),
|
||||
font_features: Some(FontFeatures::disable_ligatures()),
|
||||
font_size: Some(px(12.).into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
background_color: Some(gpui::transparent_black()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
text_style.refine(&refinement);
|
||||
|
||||
let mut editor = Editor::auto_height(10, cx);
|
||||
if let Some(message) = current_commit_message {
|
||||
editor.set_text(message, cx);
|
||||
} else {
|
||||
editor.set_text("", cx);
|
||||
}
|
||||
// editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_use_autoclose(false);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.set_show_wrap_guides(false, cx);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_text_style_refinement(refinement);
|
||||
editor.set_placeholder_text("Enter commit message", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
let buffer = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.expect("commit editor must be singleton");
|
||||
|
||||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let markdown = markdown.await.context("failed to load Markdown language")?;
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.set_language(Some(markdown), cx)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let scroll_handle = UniformListScrollHandle::new();
|
||||
|
||||
let mut this = Self {
|
||||
@@ -142,6 +203,8 @@ impl GitPanel {
|
||||
visible_entries: Vec::new(),
|
||||
current_modifiers: cx.modifiers(),
|
||||
expanded_dir_ids: Default::default(),
|
||||
git_state,
|
||||
editor,
|
||||
|
||||
width: Some(px(360.)),
|
||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
|
||||
@@ -188,12 +251,12 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn should_show_scrollbar(_cx: &AppContext) -> bool {
|
||||
// todo!(): plug into settings
|
||||
// TODO: plug into settings
|
||||
true
|
||||
}
|
||||
|
||||
fn should_autohide_scrollbar(_cx: &AppContext) -> bool {
|
||||
// todo!(): plug into settings
|
||||
// TODO: plug into settings
|
||||
true
|
||||
}
|
||||
|
||||
@@ -255,34 +318,44 @@ impl GitPanel {
|
||||
|
||||
impl GitPanel {
|
||||
fn stage_all(&mut self, _: &StageAll, _cx: &mut ViewContext<Self>) {
|
||||
// todo!(): Implement stage all
|
||||
// TODO: Implement stage all
|
||||
println!("Stage all triggered");
|
||||
}
|
||||
|
||||
fn unstage_all(&mut self, _: &UnstageAll, _cx: &mut ViewContext<Self>) {
|
||||
// todo!(): Implement unstage all
|
||||
// TODO: Implement unstage all
|
||||
println!("Unstage all triggered");
|
||||
}
|
||||
|
||||
fn discard_all(&mut self, _: &DiscardAll, _cx: &mut ViewContext<Self>) {
|
||||
// todo!(): Implement discard all
|
||||
// TODO: Implement discard all
|
||||
println!("Discard all triggered");
|
||||
}
|
||||
|
||||
fn clear_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let git_state = self.git_state.clone();
|
||||
git_state.update(cx, |state, _cx| state.clear_message());
|
||||
self.editor.update(cx, |editor, cx| editor.set_text("", cx));
|
||||
}
|
||||
|
||||
/// Commit all staged changes
|
||||
fn commit_staged_changes(&mut self, _: &CommitStagedChanges, _cx: &mut ViewContext<Self>) {
|
||||
// todo!(): Implement commit all staged
|
||||
fn commit_staged_changes(&mut self, _: &CommitStagedChanges, cx: &mut ViewContext<Self>) {
|
||||
self.clear_message(cx);
|
||||
|
||||
// TODO: Implement commit all staged
|
||||
println!("Commit staged changes triggered");
|
||||
}
|
||||
|
||||
/// Commit all changes, regardless of whether they are staged or not
|
||||
fn commit_all_changes(&mut self, _: &CommitAllChanges, _cx: &mut ViewContext<Self>) {
|
||||
// todo!(): Implement commit all changes
|
||||
fn commit_all_changes(&mut self, _: &CommitAllChanges, cx: &mut ViewContext<Self>) {
|
||||
self.clear_message(cx);
|
||||
|
||||
// TODO: Implement commit all changes
|
||||
println!("Commit all changes triggered");
|
||||
}
|
||||
|
||||
fn all_staged(&self) -> bool {
|
||||
// todo!(): Implement all_staged
|
||||
// TODO: Implement all_staged
|
||||
true
|
||||
}
|
||||
|
||||
@@ -378,7 +451,7 @@ impl GitPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!(): Update expanded directory state
|
||||
// TODO: Update expanded directory state
|
||||
fn update_visible_entries(
|
||||
&mut self,
|
||||
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
|
||||
@@ -426,6 +499,23 @@ impl GitPanel {
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
_buffer: Model<Buffer>,
|
||||
event: &language::BufferEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
||||
let commit_message = self.editor.update(cx, |editor, cx| editor.text(cx));
|
||||
|
||||
self.git_state.update(cx, |state, _cx| {
|
||||
state.commit_message = Some(commit_message.into());
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GitPanel {
|
||||
@@ -499,6 +589,13 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
pub fn render_commit_editor(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let git_state = self.git_state.clone();
|
||||
let commit_message = git_state.read(cx).commit_message.clone();
|
||||
let editor = self.editor.clone();
|
||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||
|
||||
println!("{:?}", commit_message);
|
||||
|
||||
let focus_handle_1 = self.focus_handle(cx).clone();
|
||||
let focus_handle_2 = self.focus_handle(cx).clone();
|
||||
|
||||
@@ -534,25 +631,26 @@ impl GitPanel {
|
||||
|
||||
div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
|
||||
v_flex()
|
||||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h_full()
|
||||
.py_2p5()
|
||||
.px_3()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.font_buffer(cx)
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child("Add a message")
|
||||
.gap_1()
|
||||
.child(div().flex_grow())
|
||||
.child(h_flex().child(div().gap_1().flex_grow()).child(
|
||||
if self.current_modifiers.alt {
|
||||
commit_all_button
|
||||
} else {
|
||||
commit_staged_button
|
||||
},
|
||||
))
|
||||
.cursor(CursorStyle::OperationNotAllowed)
|
||||
.opacity(0.5),
|
||||
.on_click(cx.listener(move |_, _: &ClickEvent, cx| cx.focus(&editor_focus_handle)))
|
||||
.child(self.editor.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.bottom_2p5()
|
||||
.right_3()
|
||||
.child(div().gap_1().flex_grow())
|
||||
.child(if self.current_modifiers.alt {
|
||||
commit_all_button
|
||||
} else {
|
||||
commit_staged_button
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ::settings::Settings;
|
||||
use git::repository::GitFileStatus;
|
||||
use gpui::{actions, AppContext, Hsla};
|
||||
use gpui::{actions, prelude::*, AppContext, Global, Hsla, Model};
|
||||
use settings::GitPanelSettings;
|
||||
use ui::{Color, Icon, IconName, IntoElement};
|
||||
use ui::{Color, Icon, IconName, IntoElement, SharedString};
|
||||
|
||||
pub mod git_panel;
|
||||
mod settings;
|
||||
@@ -14,14 +14,50 @@ actions!(
|
||||
UnstageAll,
|
||||
DiscardAll,
|
||||
CommitStagedChanges,
|
||||
CommitAllChanges
|
||||
CommitAllChanges,
|
||||
ClearMessage
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
GitPanelSettings::register(cx);
|
||||
let git_state = cx.new_model(|_cx| GitState::new());
|
||||
cx.set_global(GlobalGitState(git_state));
|
||||
}
|
||||
|
||||
struct GlobalGitState(Model<GitState>);
|
||||
|
||||
impl Global for GlobalGitState {}
|
||||
|
||||
pub struct GitState {
|
||||
commit_message: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl GitState {
|
||||
pub fn new() -> Self {
|
||||
GitState {
|
||||
commit_message: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_message(&mut self, message: Option<SharedString>) {
|
||||
self.commit_message = message;
|
||||
}
|
||||
|
||||
pub fn clear_message(&mut self) {
|
||||
self.commit_message = None;
|
||||
}
|
||||
|
||||
pub fn get_global(cx: &mut AppContext) -> Model<GitState> {
|
||||
cx.global::<GlobalGitState>().0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// impl EventEmitter<Event> for GitState {}
|
||||
|
||||
// #[derive(Clone, Debug, PartialEq, Eq)]
|
||||
// pub enum Event {}
|
||||
|
||||
const ADDED_COLOR: Hsla = Hsla {
|
||||
h: 142. / 360.,
|
||||
s: 0.68,
|
||||
@@ -41,7 +77,7 @@ const REMOVED_COLOR: Hsla = Hsla {
|
||||
a: 1.0,
|
||||
};
|
||||
|
||||
// todo!(): Add updated status colors to theme
|
||||
// TODO: Add updated status colors to theme
|
||||
pub fn git_status_icon(status: GitFileStatus) -> impl IntoElement {
|
||||
match status {
|
||||
GitFileStatus::Added => Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR)),
|
||||
|
||||
@@ -315,7 +315,10 @@ impl ContextProvider for PythonContextProvider {
|
||||
toolchains
|
||||
.active_toolchain(worktree_id, "Python".into(), &mut cx)
|
||||
.await
|
||||
.map_or_else(|| "python3".to_owned(), |toolchain| toolchain.path.into())
|
||||
.map_or_else(
|
||||
|| "python3".to_owned(),
|
||||
|toolchain| format!("\"{}\"", toolchain.path),
|
||||
)
|
||||
} else {
|
||||
String::from("python3")
|
||||
};
|
||||
@@ -336,14 +339,17 @@ impl ContextProvider for PythonContextProvider {
|
||||
TaskTemplate {
|
||||
label: "execute selection".to_owned(),
|
||||
command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
|
||||
args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()],
|
||||
args: vec![
|
||||
"-c".to_owned(),
|
||||
VariableName::SelectedText.template_value_with_whitespace(),
|
||||
],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
// Execute an entire file
|
||||
TaskTemplate {
|
||||
label: format!("run '{}'", VariableName::File.template_value()),
|
||||
command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
|
||||
args: vec![VariableName::File.template_value()],
|
||||
args: vec![VariableName::File.template_value_with_whitespace()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
];
|
||||
@@ -358,7 +364,7 @@ impl ContextProvider for PythonContextProvider {
|
||||
args: vec![
|
||||
"-m".to_owned(),
|
||||
"unittest".to_owned(),
|
||||
VariableName::File.template_value(),
|
||||
VariableName::File.template_value_with_whitespace(),
|
||||
],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
@@ -369,7 +375,7 @@ impl ContextProvider for PythonContextProvider {
|
||||
args: vec![
|
||||
"-m".to_owned(),
|
||||
"unittest".to_owned(),
|
||||
"$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(),
|
||||
PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace()
|
||||
],
|
||||
tags: vec![
|
||||
"python-unittest-class".to_owned(),
|
||||
@@ -388,7 +394,7 @@ impl ContextProvider for PythonContextProvider {
|
||||
args: vec![
|
||||
"-m".to_owned(),
|
||||
"pytest".to_owned(),
|
||||
VariableName::File.template_value(),
|
||||
VariableName::File.template_value_with_whitespace(),
|
||||
],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
@@ -399,7 +405,7 @@ impl ContextProvider for PythonContextProvider {
|
||||
args: vec![
|
||||
"-m".to_owned(),
|
||||
"pytest".to_owned(),
|
||||
"$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(),
|
||||
PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace(),
|
||||
],
|
||||
tags: vec![
|
||||
"python-pytest-class".to_owned(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{AppContext, FontFeatures, FontWeight};
|
||||
use settings::{EditableSettingControl, Settings};
|
||||
use theme::{FontFamilyCache, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, ToggleWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
|
||||
prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
|
||||
SettingsGroup, ToggleButton,
|
||||
};
|
||||
|
||||
@@ -368,7 +368,7 @@ impl RenderOnce for UiFontLigaturesControl {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let value = Self::read(cx);
|
||||
|
||||
ToggleWithLabel::new(
|
||||
CheckboxWithLabel::new(
|
||||
"ui-font-ligatures",
|
||||
Label::new(self.name()),
|
||||
value.into(),
|
||||
|
||||
@@ -135,6 +135,10 @@ impl VariableName {
|
||||
pub fn template_value(&self) -> String {
|
||||
format!("${self}")
|
||||
}
|
||||
/// Generates a `"$VARIABLE"`-like string, to be used instead of `Self::template_value` when expanded value could contain spaces or special characters.
|
||||
pub fn template_value_with_whitespace(&self) -> String {
|
||||
format!("\"${self}\"")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VariableName {
|
||||
|
||||
@@ -17,38 +17,6 @@ pub fn switch(id: impl Into<ElementId>, toggle_state: ToggleState) -> Switch {
|
||||
Switch::new(id, toggle_state)
|
||||
}
|
||||
|
||||
/// Creates a new checkbox with a label
|
||||
///
|
||||
/// [`ToggleWithLabel`] with [`ToggleKind::Checkbox`]
|
||||
pub fn labeled_checkbox(
|
||||
id: impl Into<ElementId>,
|
||||
label: Label,
|
||||
toggle_state: ToggleState,
|
||||
on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,
|
||||
) -> ToggleWithLabel {
|
||||
ToggleWithLabel::new(id, label, toggle_state, on_click).kind(ToggleKind::Checkbox)
|
||||
}
|
||||
|
||||
/// Creates a new switch with a label
|
||||
///
|
||||
/// [`ToggleWithLabel`] with [`ToggleKind::Switch`]
|
||||
pub fn labeled_switch(
|
||||
id: impl Into<ElementId>,
|
||||
label: Label,
|
||||
toggle_state: ToggleState,
|
||||
on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,
|
||||
) -> ToggleWithLabel {
|
||||
ToggleWithLabel::new(id, label, toggle_state, on_click).kind(ToggleKind::Switch)
|
||||
}
|
||||
|
||||
/// The types of toggles, defaulting to [`Checkbox`]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ToggleKind {
|
||||
#[default]
|
||||
Checkbox,
|
||||
Switch,
|
||||
}
|
||||
|
||||
/// # Checkbox
|
||||
///
|
||||
/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
|
||||
@@ -164,15 +132,14 @@ impl RenderOnce for Checkbox {
|
||||
|
||||
/// A [`Checkbox`] that has a [`Label`].
|
||||
#[derive(IntoElement)]
|
||||
pub struct ToggleWithLabel {
|
||||
pub struct CheckboxWithLabel {
|
||||
id: ElementId,
|
||||
label: Label,
|
||||
kind: ToggleKind,
|
||||
checked: ToggleState,
|
||||
on_click: Arc<dyn Fn(&ToggleState, &mut WindowContext) + 'static>,
|
||||
}
|
||||
|
||||
impl ToggleWithLabel {
|
||||
impl CheckboxWithLabel {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
label: Label,
|
||||
@@ -182,38 +149,22 @@ impl ToggleWithLabel {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label,
|
||||
kind: ToggleKind::default(),
|
||||
checked,
|
||||
on_click: Arc::new(on_click),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(mut self, kind: ToggleKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ToggleWithLabel {
|
||||
impl RenderOnce for CheckboxWithLabel {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.when(self.kind == ToggleKind::Checkbox, |this| {
|
||||
this.child(Checkbox::new(self.id.clone(), self.checked).on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, cx| {
|
||||
(on_click)(checked, cx);
|
||||
}
|
||||
}))
|
||||
})
|
||||
.when(self.kind == ToggleKind::Switch, |this| {
|
||||
this.child(Switch::new(self.id.clone(), self.checked).on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, cx| {
|
||||
(on_click)(checked, cx);
|
||||
}
|
||||
}))
|
||||
})
|
||||
.child(Checkbox::new(self.id.clone(), self.checked).on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, cx| {
|
||||
(on_click)(checked, cx);
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from(format!("{}-label", self.id)))
|
||||
@@ -419,70 +370,40 @@ impl ComponentPreview for Switch {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for ToggleWithLabel {
|
||||
impl ComponentPreview for CheckboxWithLabel {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A toggle with an associated label, allowing users to select an option while providing a descriptive text. By default, the toggle is presented as a checkbox, but can also render a switch."
|
||||
"A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
|
||||
}
|
||||
|
||||
fn examples(_: &mut WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Checkbox",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
ToggleWithLabel::new(
|
||||
"checkbox_with_label_unselected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Unselected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
ToggleWithLabel::new(
|
||||
"checkbox_with_label_indeterminate",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Indeterminate,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
ToggleWithLabel::new(
|
||||
"checkbox_with_label_selected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Selected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
],
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_unselected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Unselected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
example_group_with_title(
|
||||
"Switch",
|
||||
vec![
|
||||
single_example(
|
||||
"Off",
|
||||
ToggleWithLabel::new(
|
||||
"switch_with_label_off",
|
||||
Label::new("Dark mode"),
|
||||
ToggleState::Unselected,
|
||||
|_, _| {},
|
||||
)
|
||||
.kind(ToggleKind::Switch),
|
||||
),
|
||||
single_example(
|
||||
"On",
|
||||
ToggleWithLabel::new(
|
||||
"switch_with_label_on",
|
||||
Label::new("Dark mode"),
|
||||
ToggleState::Selected,
|
||||
|_, _| {},
|
||||
)
|
||||
.kind(ToggleKind::Switch),
|
||||
),
|
||||
],
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_indeterminate",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Indeterminate,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
]
|
||||
single_example(
|
||||
"Selected",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_selected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Selected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
])]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use ui::{labeled_checkbox, prelude::*, Tooltip};
|
||||
use ui::{prelude::*, CheckboxWithLabel, Tooltip};
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::{
|
||||
dock::DockPosition,
|
||||
@@ -269,26 +269,24 @@ impl Render for WelcomePage {
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
labeled_checkbox(
|
||||
"enable-vim",
|
||||
Label::new("Enable Vim Mode"),
|
||||
if VimModeSetting::get_global(cx).0 {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("welcome page: toggle vim".to_string());
|
||||
this.update_settings::<VimModeSetting>(
|
||||
selection,
|
||||
cx,
|
||||
|setting, value| *setting = Some(value),
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
.child(CheckboxWithLabel::new(
|
||||
"enable-vim",
|
||||
Label::new("Enable Vim Mode"),
|
||||
if VimModeSetting::get_global(cx).0 {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry
|
||||
.report_app_event("welcome page: toggle vim".to_string());
|
||||
this.update_settings::<VimModeSetting>(
|
||||
selection,
|
||||
cx,
|
||||
|setting, value| *setting = Some(value),
|
||||
);
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
IconButton::new("vim-mode", IconName::Info)
|
||||
.icon_size(IconSize::XSmall)
|
||||
@@ -296,62 +294,58 @@ impl Render for WelcomePage {
|
||||
.tooltip(|cx| Tooltip::text("You can also toggle Vim Mode via the command palette or Editor Controls menu.", cx)),
|
||||
)
|
||||
)
|
||||
.child(
|
||||
labeled_checkbox(
|
||||
"enable-crash",
|
||||
Label::new("Send Crash Reports"),
|
||||
if TelemetrySettings::get_global(cx).diagnostics {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: toggle diagnostic telemetry".to_string(),
|
||||
);
|
||||
this.update_settings::<TelemetrySettings>(selection, cx, {
|
||||
let telemetry = this.telemetry.clone();
|
||||
.child(CheckboxWithLabel::new(
|
||||
"enable-crash",
|
||||
Label::new("Send Crash Reports"),
|
||||
if TelemetrySettings::get_global(cx).diagnostics {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: toggle diagnostic telemetry".to_string(),
|
||||
);
|
||||
this.update_settings::<TelemetrySettings>(selection, cx, {
|
||||
let telemetry = this.telemetry.clone();
|
||||
|
||||
move |settings, value| {
|
||||
settings.diagnostics = Some(value);
|
||||
move |settings, value| {
|
||||
settings.diagnostics = Some(value);
|
||||
|
||||
telemetry.report_setting_event(
|
||||
"diagnostic telemetry",
|
||||
value.to_string(),
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
.child(
|
||||
labeled_checkbox(
|
||||
"enable-telemetry",
|
||||
Label::new("Send Telemetry"),
|
||||
if TelemetrySettings::get_global(cx).metrics {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: toggle metric telemetry".to_string(),
|
||||
);
|
||||
this.update_settings::<TelemetrySettings>(selection, cx, {
|
||||
let telemetry = this.telemetry.clone();
|
||||
telemetry.report_setting_event(
|
||||
"diagnostic telemetry",
|
||||
value.to_string(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}),
|
||||
))
|
||||
.child(CheckboxWithLabel::new(
|
||||
"enable-telemetry",
|
||||
Label::new("Send Telemetry"),
|
||||
if TelemetrySettings::get_global(cx).metrics {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
cx.listener(move |this, selection, cx| {
|
||||
this.telemetry.report_app_event(
|
||||
"welcome page: toggle metric telemetry".to_string(),
|
||||
);
|
||||
this.update_settings::<TelemetrySettings>(selection, cx, {
|
||||
let telemetry = this.telemetry.clone();
|
||||
|
||||
move |settings, value| {
|
||||
settings.metrics = Some(value);
|
||||
move |settings, value| {
|
||||
settings.metrics = Some(value);
|
||||
|
||||
telemetry.report_setting_event(
|
||||
"metric telemetry",
|
||||
value.to_string(),
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
),
|
||||
telemetry.report_setting_event(
|
||||
"metric telemetry",
|
||||
value.to_string(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}),
|
||||
)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ use theme::all_theme_colors;
|
||||
use ui::{
|
||||
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
|
||||
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
|
||||
Checkbox, ContentGroup, DecoratedIcon, ElevationIndex, Facepile, IconDecoration, Indicator,
|
||||
Switch, Table, TintColor, ToggleWithLabel, Tooltip,
|
||||
Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
|
||||
IconDecoration, Indicator, Switch, Table, TintColor, Tooltip,
|
||||
};
|
||||
|
||||
use crate::{Item, Workspace};
|
||||
@@ -369,12 +369,12 @@ impl ThemePreview {
|
||||
.overflow_scroll()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Switch::render_component_previews(cx))
|
||||
.child(ContentGroup::render_component_previews(cx))
|
||||
.child(IconDecoration::render_component_previews(cx))
|
||||
.child(DecoratedIcon::render_component_previews(cx))
|
||||
.child(Checkbox::render_component_previews(cx))
|
||||
.child(Switch::render_component_previews(cx))
|
||||
.child(ToggleWithLabel::render_component_previews(cx))
|
||||
.child(CheckboxWithLabel::render_component_previews(cx))
|
||||
.child(Facepile::render_component_previews(cx))
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Indicator::render_component_previews(cx))
|
||||
|
||||
@@ -927,7 +927,7 @@ impl ZetaInlineCompletionProvider {
|
||||
|
||||
impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvider {
|
||||
fn name() -> &'static str {
|
||||
"Zeta"
|
||||
"zeta"
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
|
||||
Reference in New Issue
Block a user