Compare commits

..

26 Commits

Author SHA1 Message Date
Max Brunsfeld
18ff459014 collab 0.2.2 2022-11-04 10:04:14 -07:00
Max Brunsfeld
927cfa44db Use Rust 1.65 in collab docker image 2022-11-04 10:03:47 -07:00
Antonio Scandurra
7301ab4f44 Merge pull request #1854 from zed-industries/command-palette-improvements
Improve styling of command palettes
2022-11-04 14:09:55 +00:00
Antonio Scandurra
0b231e58fd Show placeholder text for pickers 2022-11-04 10:18:47 +01:00
Antonio Scandurra
08b84416d2 Avoid showing "No matches" when query is empty if there are no matches 2022-11-04 10:18:47 +01:00
Antonio Scandurra
aec8aec800 Show the cursor right away when opening opening/focusing editors 2022-11-04 10:18:47 +01:00
Max Brunsfeld
5bcf9916c9 Merge pull request #1853 from zed-industries/diagnostics-focus-loop
Fix infinite focus transfer loop in project diagnostics
2022-11-03 18:01:14 -07:00
Max Brunsfeld
6076a3fc61 Fix infinite focus transfer loop in project diagnostics
Co-authored-by: Kay Simmons <kay@zed.dev>
2022-11-03 17:53:36 -07:00
Max Brunsfeld
05389dc239 Merge pull request #1844 from zed-industries/ruby
Add support for Ruby
2022-11-03 15:59:11 -07:00
Max Brunsfeld
d222904471 Add basic support for ruby
Co-authored-by: Kay Simmons <kay@zed.dev>
2022-11-03 15:52:33 -07:00
Max Brunsfeld
9f3ea0c87f Merge pull request #1851 from zed-industries/rust-let-else
Bump tree-sitter-rust for let-else, let-chains
2022-11-03 12:39:37 -07:00
Max Brunsfeld
601ec40ddc Bump tree-sitter-rust for let-else, let-chains 2022-11-03 12:35:12 -07:00
Joseph T. Lyons
adc4a5984e Merge pull request #1850 from zed-industries/add-automatic-annotations-for-mixpanel
Add automatic annotations for mixpanel
2022-11-03 13:26:15 -04:00
Joseph T Lyons
0f78174d78 Use folded style to make arguments easier to read 2022-11-03 13:06:19 -04:00
Joseph T Lyons
ad67a1b744 Add automatic annotations for mixpanel 2022-11-03 01:48:49 -04:00
Nate Butler
edc2966651 Merge pull request #1847 from zed-industries/readd-abruzzo-theme
Re-add Abruzzo theme to experimental themes
2022-11-02 22:34:04 -04:00
Joseph T. Lyons
eacfa856cf Merge pull request #1848 from zed-industries/fix-markdown-rendering-of-discord-release-notes-webhook
Fix markdown rendering of discord release notes webhook
2022-11-02 16:12:29 -04:00
Max Brunsfeld
fe4862d756 Fix error in bump-zed-patch-version script 2022-11-02 12:18:14 -07:00
Max Brunsfeld
8312d974ac Fix release channel on stable 2022-11-02 12:18:07 -07:00
Max Brunsfeld
6d3bd495fc Fix error case in what-is-deployed script 2022-11-02 12:05:37 -07:00
Joseph T Lyons
576e350bea Fix markdown rendering of discord release notes webhook 2022-11-02 14:55:27 -04:00
Max Brunsfeld
cc1325d6f9 Adjust script for getting changes to put in release notes
Now, this script is only useful for the preview channel's releases. The
stable channel's release notes can be mostly copied from the existing
preview releases notes.

Co-authored-by: Joseph Lyons <joseph@zed.dev>
2022-11-02 10:55:48 -07:00
Max Brunsfeld
c411cb7eef collab 0.2.1 2022-11-02 10:27:26 -07:00
Max Brunsfeld
c9ba41d002 Fix errors in bump-zed-minor-versions script
Co-authored-by: Joseph Lyons <joseph@zed.dev>
2022-11-02 10:25:20 -07:00
Joseph T Lyons
3caa7a4916 v0.64.x dev 2022-11-02 12:57:53 -04:00
Nate Butler
4f344f1ac1 Re-add Abruzzo theme to experimental themes 2022-11-02 11:28:43 -04:00
60 changed files with 694 additions and 288 deletions

View File

@@ -13,12 +13,12 @@ jobs:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: |
📣 Zed ${{ github.event.release.tag_name }} was just released!
Restart your Zed or head to https://zed.dev/releases to grab it.
```md
### Changelog
${{ github.event.release.body }}
```
mixpanel_release:
@@ -31,7 +31,7 @@ jobs:
architecture: "x64"
cache: "pip"
- run: pip install -r script/mixpanel_release/requirements.txt
- run: >
- run: >
python script/mixpanel_release/main.py
${{ github.event.release.tag_name }}
${{ secrets.MIXPANEL_PROJECT_ID }}

17
Cargo.lock generated
View File

@@ -1028,7 +1028,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
version = "0.2.2"
dependencies = [
"anyhow",
"async-trait",
@@ -3009,6 +3009,7 @@ dependencies = [
"tree-sitter-javascript",
"tree-sitter-json 0.19.0",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-typescript",
"unindent",
@@ -4011,6 +4012,7 @@ dependencies = [
"env_logger",
"gpui",
"menu",
"parking_lot 0.11.2",
"serde_json",
"settings",
"theme",
@@ -6491,6 +6493,16 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-ruby"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ac30cbb1560363ae76e1ccde543d6d99087421e228cc47afcec004b86bb711a"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-rust"
version = "0.20.3"
@@ -7628,7 +7640,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.63.2"
version = "0.64.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -7712,6 +7724,7 @@ dependencies = [
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-toml",
"tree-sitter-typescript",

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.64-bullseye as builder
FROM rust:1.65-bullseye as builder
WORKDIR app
COPY . .

View File

@@ -32,6 +32,7 @@ pub struct Telemetry {
struct TelemetryState {
metrics_id: Option<Arc<str>>,
device_id: Option<Arc<str>>,
app: &'static str,
app_version: Option<Arc<str>>,
release_channel: Option<&'static str>,
os_version: Option<Arc<str>>,
@@ -79,6 +80,8 @@ struct MixpanelEventProperties {
app_version: Option<Arc<str>>,
#[serde(rename = "Signed In")]
signed_in: bool,
#[serde(rename = "App")]
app: &'static str,
}
#[derive(Serialize)]
@@ -117,6 +120,7 @@ impl Telemetry {
state: Mutex::new(TelemetryState {
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
os_name: platform.os_name().into(),
app: "Zed",
app_version: platform.app_version().ok().map(|v| v.to_string().into()),
release_channel,
device_id: None,
@@ -201,11 +205,7 @@ impl Telemetry {
let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest {
token,
distinct_id: device_id,
set: json!({
"Staff": is_staff,
"ID": metrics_id,
"App": true
}),
set: json!({ "Staff": is_staff, "ID": metrics_id }),
}])?;
let request = Request::post(MIXPANEL_ENGAGE_URL)
.header("Content-Type", "application/json")
@@ -241,6 +241,7 @@ impl Telemetry {
release_channel: state.release_channel,
app_version: state.app_version.clone(),
signed_in: state.metrics_id.is_some(),
app: state.app,
},
};
state.queue.push(event);

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.2.0"
version = "0.2.2"
[[bin]]
name = "collab"

View File

@@ -170,8 +170,8 @@ impl ContactFinder {
let this = cx.weak_handle();
Self {
picker: cx.add_view(|cx| {
Picker::new(this, cx)
.with_theme(|cx| &cx.global::<Settings>().theme.contact_finder.picker)
Picker::new("Search collaborator by username...", this, cx)
.with_theme(|theme| theme.contact_finder.picker.clone())
}),
potential_contacts: Arc::from([]),
user_store,

View File

@@ -175,7 +175,9 @@ impl ContactList {
) -> Self {
let filter_editor = cx.add_view(|cx| {
let mut editor = Editor::single_line(
Some(|theme| theme.contact_list.user_query_editor.clone()),
Some(Arc::new(|theme| {
theme.contact_list.user_query_editor.clone()
})),
cx,
);
editor.set_placeholder_text("Filter contacts", cx);

View File

@@ -70,7 +70,7 @@ impl CommandPalette {
})
.collect();
let picker = cx.add_view(|cx| Picker::new(this, cx));
let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
Self {
picker,
actions,

View File

@@ -93,6 +93,9 @@ impl BlinkManager {
pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
self.enabled = true;
// Set cursors as invisible and start blinking: this causes cursors
// to be visible during the next render.
self.visible = false;
self.blink_cursors(self.blink_epoch, cx);
}

View File

@@ -437,8 +437,7 @@ pub struct EditorStyle {
type CompletionId = usize;
pub type GetFieldEditorTheme = fn(&theme::Theme) -> theme::FieldEditor;
type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
#[derive(Clone, Copy)]
@@ -523,7 +522,7 @@ pub struct Editor {
scroll_top_anchor: Anchor,
autoscroll_request: Option<(Autoscroll, bool)>,
soft_wrap_mode_override: Option<settings::SoftWrap>,
get_field_editor_theme: Option<GetFieldEditorTheme>,
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<ModelHandle<Project>>,
focused: bool,
@@ -1070,7 +1069,7 @@ enum GotoDefinitionKind {
impl Editor {
pub fn single_line(
field_editor_style: Option<GetFieldEditorTheme>,
field_editor_style: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
@@ -1080,7 +1079,7 @@ impl Editor {
pub fn auto_height(
max_lines: usize,
field_editor_style: Option<GetFieldEditorTheme>,
field_editor_style: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
@@ -1116,7 +1115,7 @@ impl Editor {
self.mode,
self.buffer.clone(),
self.project.clone(),
self.get_field_editor_theme,
self.get_field_editor_theme.clone(),
cx,
);
self.display_map.update(cx, |display_map, cx| {
@@ -1136,12 +1135,12 @@ impl Editor {
mode: EditorMode,
buffer: ModelHandle<MultiBuffer>,
project: Option<ModelHandle<Project>>,
get_field_editor_theme: Option<GetFieldEditorTheme>,
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let display_map = cx.add_model(|cx| {
let settings = cx.global::<Settings>();
let style = build_style(&*settings, get_field_editor_theme, None, cx);
let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
DisplayMap::new(
buffer.clone(),
style.text.font_id,
@@ -1289,7 +1288,7 @@ impl Editor {
fn style(&self, cx: &AppContext) -> EditorStyle {
build_style(
cx.global::<Settings>(),
self.get_field_editor_theme,
self.get_field_editor_theme.as_deref(),
self.override_text_style.as_deref(),
cx,
)
@@ -6846,7 +6845,7 @@ impl View for Editor {
fn build_style(
settings: &Settings,
get_field_editor_theme: Option<GetFieldEditorTheme>,
get_field_editor_theme: Option<&GetFieldEditorTheme>,
override_text_style: Option<&OverrideTextStyle>,
cx: &AppContext,
) -> EditorStyle {

View File

@@ -1186,7 +1186,7 @@ impl EditorElement {
}
// When the editor is empty and unfocused, then show the placeholder.
if snapshot.is_empty() && !snapshot.is_focused() {
if snapshot.is_empty() {
let placeholder_style = self
.style
.placeholder_text

View File

@@ -677,19 +677,6 @@ impl<'a> MutableSelectionsCollection<'a> {
});
}
pub fn maybe_move_cursors_with(
&mut self,
mut update_cursor_position: impl FnMut(
&DisplaySnapshot,
DisplayPoint,
SelectionGoal,
) -> Option<(DisplayPoint, SelectionGoal)>,
) {
self.move_cursors_with(|map, point, goal| {
update_cursor_position(map, point, goal).unwrap_or((point, goal))
})
}
pub fn replace_cursors_with(
&mut self,
mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec<DisplayPoint>,

View File

@@ -119,7 +119,7 @@ impl FileFinder {
cx.observe(&project, Self::project_updated).detach();
Self {
project,
picker: cx.add_view(|cx| Picker::new(handle, cx)),
picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
search_count: 0,
latest_search_id: 0,
latest_search_did_cancel: false,

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
use gpui::{
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
@@ -31,7 +33,10 @@ pub enum Event {
impl GoToLine {
pub fn new(active_editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) -> Self {
let line_editor = cx.add_view(|cx| {
Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
Editor::single_line(
Some(Arc::new(|theme| theme.picker.input_editor.clone())),
cx,
)
});
cx.subscribe(&line_editor, Self::on_line_editor_event)
.detach();
@@ -170,8 +175,8 @@ impl View for GoToLine {
.boxed(),
)
.with_child(
Container::new(Label::new(label, theme.empty.label.clone()).boxed())
.with_style(theme.empty.container)
Container::new(Label::new(label, theme.no_matches.label.clone()).boxed())
.with_style(theme.no_matches.container)
.boxed(),
)
.boxed(),

View File

@@ -71,4 +71,5 @@ tree-sitter-json = "*"
tree-sitter-rust = "*"
tree-sitter-python = "*"
tree-sitter-typescript = "*"
tree-sitter-ruby = "*"
unindent = "0.1.7"

View File

@@ -1764,6 +1764,7 @@ impl BufferSnapshot {
.collect::<Vec<_>>();
let mut indent_ranges = Vec::<Range<Point>>::new();
let mut outdent_positions = Vec::<Point>::new();
while let Some(mat) = matches.peek() {
let mut start: Option<Point> = None;
let mut end: Option<Point> = None;
@@ -1777,6 +1778,8 @@ impl BufferSnapshot {
start = Some(Point::from_ts_point(capture.node.end_position()));
} else if Some(capture.index) == config.end_capture_ix {
end = Some(Point::from_ts_point(capture.node.start_position()));
} else if Some(capture.index) == config.outdent_capture_ix {
outdent_positions.push(Point::from_ts_point(capture.node.start_position()));
}
}
@@ -1797,6 +1800,19 @@ impl BufferSnapshot {
}
}
outdent_positions.sort();
for outdent_position in outdent_positions {
// find the innermost indent range containing this outdent_position
// set its end to the outdent position
if let Some(range_to_truncate) = indent_ranges
.iter_mut()
.filter(|indent_range| indent_range.contains(&outdent_position))
.last()
{
range_to_truncate.end = outdent_position;
}
}
// Find the suggested indentation increases and decreased based on regexes.
let mut indent_change_rows = Vec::<(u32, Ordering)>::new();
self.for_each_line(

View File

@@ -1150,6 +1150,49 @@ fn test_autoindent_with_injected_languages(cx: &mut MutableAppContext) {
});
}
#[gpui::test]
fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) {
let mut settings = Settings::test(cx);
settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(ruby_lang()), cx);
let text = r#"
class C
def a(b, c)
puts b
puts c
rescue
puts "errored"
exit 1
end
end
"#
.unindent();
buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
assert_eq!(
buffer.text(),
r#"
class C
def a(b, c)
puts b
puts c
rescue
puts "errored"
exit 1
end
end
"#
.unindent()
);
buffer
});
}
#[gpui::test]
fn test_serialization(cx: &mut gpui::MutableAppContext) {
let mut now = Instant::now();
@@ -1497,6 +1540,26 @@ impl Buffer {
}
}
fn ruby_lang() -> Language {
Language::new(
LanguageConfig {
name: "Ruby".into(),
path_suffixes: vec!["rb".to_string()],
..Default::default()
},
Some(tree_sitter_ruby::language()),
)
.with_indents_query(
r#"
(class "end" @end) @indent
(method "end" @end) @indent
(rescue) @outdent
(then) @indent
"#,
)
.unwrap()
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {

View File

@@ -312,6 +312,7 @@ struct IndentConfig {
indent_capture_ix: u32,
start_capture_ix: Option<u32>,
end_capture_ix: Option<u32>,
outdent_capture_ix: Option<u32>,
}
struct OutlineConfig {
@@ -670,12 +671,14 @@ impl Language {
let mut indent_capture_ix = None;
let mut start_capture_ix = None;
let mut end_capture_ix = None;
let mut outdent_capture_ix = None;
get_capture_indices(
&query,
&mut [
("indent", &mut indent_capture_ix),
("start", &mut start_capture_ix),
("end", &mut end_capture_ix),
("outdent", &mut outdent_capture_ix),
],
);
if let Some(indent_capture_ix) = indent_capture_ix {
@@ -684,6 +687,7 @@ impl Language {
indent_capture_ix,
start_capture_ix,
end_capture_ix,
outdent_capture_ix,
});
}
Ok(self)

View File

@@ -67,7 +67,9 @@ impl OutlineView {
) -> Self {
let handle = cx.weak_handle();
Self {
picker: cx.add_view(|cx| Picker::new(handle, cx).with_max_size(800., 1200.)),
picker: cx.add_view(|cx| {
Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
}),
last_query: Default::default(),
matches: Default::default(),
selected_match_index: 0,

View File

@@ -16,6 +16,8 @@ util = { path = "../util" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
parking_lot = "0.11.1"
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
serde_json = { version = "1.0", features = ["preserve_order"] }

View File

@@ -1,25 +1,22 @@
use editor::Editor;
use gpui::{
elements::{
ChildView, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, UniformList,
UniformListState,
},
elements::*,
geometry::vector::{vec2f, Vector2F},
keymap,
platform::CursorStyle,
AnyViewHandle, AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState,
MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
AnyViewHandle, AppContext, Axis, Entity, MouseButton, MouseState, MutableAppContext,
RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
use settings::Settings;
use std::cmp;
use parking_lot::Mutex;
use std::{cmp, sync::Arc};
pub struct Picker<D: PickerDelegate> {
delegate: WeakViewHandle<D>,
query_editor: ViewHandle<Editor>,
list_state: UniformListState,
max_size: Vector2F,
theme: Box<dyn FnMut(&AppContext) -> &theme::Picker>,
theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
confirmed: bool,
}
@@ -52,8 +49,8 @@ impl<D: PickerDelegate> View for Picker<D> {
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
let theme = (self.theme)(cx);
let container_style = theme.container;
let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
let query = self.query(cx);
let delegate = self.delegate.clone();
let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
delegate.read(cx).match_count()
@@ -61,19 +58,36 @@ impl<D: PickerDelegate> View for Picker<D> {
0
};
let container_style;
let editor_style;
if query.is_empty() && match_count == 0 {
container_style = theme.empty_container;
editor_style = theme.empty_input_editor.container;
} else {
container_style = theme.container;
editor_style = theme.input_editor.container;
};
Flex::new(Axis::Vertical)
.with_child(
ChildView::new(&self.query_editor, cx)
.contained()
.with_style(theme.input_editor.container)
.with_style(editor_style)
.boxed(),
)
.with_child(
if match_count == 0 {
Label::new("No matches".into(), theme.empty.label.clone())
.contained()
.with_style(theme.empty.container)
.with_children(if match_count == 0 {
if query.is_empty() {
None
} else {
Some(
Label::new("No matches".into(), theme.no_matches.label.clone())
.contained()
.with_style(theme.no_matches.container)
.boxed(),
)
}
} else {
Some(
UniformList::new(
self.list_state.clone(),
match_count,
@@ -98,10 +112,10 @@ impl<D: PickerDelegate> View for Picker<D> {
)
.contained()
.with_margin_top(6.0)
}
.flex(1., false)
.boxed(),
)
.flex(1., false)
.boxed(),
)
})
.contained()
.with_style(container_style)
.constrained()
@@ -134,9 +148,26 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::cancel);
}
pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
let query_editor = cx.add_view(|cx| {
Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
where
P: Into<Arc<str>>,
{
let theme = Arc::new(Mutex::new(
Box::new(|theme: &theme::Theme| theme.picker.clone())
as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
));
let query_editor = cx.add_view({
let picker_theme = theme.clone();
|cx| {
let mut editor = Editor::single_line(
Some(Arc::new(move |theme| {
(picker_theme.lock())(theme).input_editor.clone()
})),
cx,
);
editor.set_placeholder_text(placeholder, cx);
editor
}
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
@@ -145,7 +176,7 @@ impl<D: PickerDelegate> Picker<D> {
list_state: Default::default(),
delegate,
max_size: vec2f(540., 420.),
theme: Box::new(|cx| &cx.global::<Settings>().theme.picker),
theme,
confirmed: false,
};
cx.defer(|this, cx| {
@@ -162,11 +193,11 @@ impl<D: PickerDelegate> Picker<D> {
self
}
pub fn with_theme<F>(mut self, theme: F) -> Self
pub fn with_theme<F>(self, theme: F) -> Self
where
F: 'static + FnMut(&AppContext) -> &theme::Picker,
F: 'static + Fn(&theme::Theme) -> theme::Picker,
{
self.theme = Box::new(theme);
*self.theme.lock() = Box::new(theme);
self
}

View File

@@ -23,6 +23,7 @@ use std::{
ffi::OsStr,
ops::Range,
path::{Path, PathBuf},
sync::Arc,
};
use unicase::UniCase;
use workspace::Workspace;
@@ -175,11 +176,11 @@ impl ProjectPanel {
let filename_editor = cx.add_view(|cx| {
Editor::single_line(
Some(|theme| {
Some(Arc::new(|theme| {
let mut style = theme.project_panel.filename_editor.clone();
style.container.background_color.take();
style
}),
})),
cx,
)
});

View File

@@ -63,7 +63,7 @@ impl ProjectSymbolsView {
let handle = cx.weak_handle();
Self {
project,
picker: cx.add_view(|cx| Picker::new(handle, cx)),
picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
selected_match_index: 0,
symbols: Default::default(),
visible_match_candidates: Default::default(),

View File

@@ -12,7 +12,7 @@ use gpui::{
use project::search::SearchQuery;
use serde::Deserialize;
use settings::Settings;
use std::any::Any;
use std::{any::Any, sync::Arc};
use workspace::{
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView,
@@ -232,7 +232,11 @@ impl ToolbarItemView for BufferSearchBar {
impl BufferSearchBar {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let query_editor = cx.add_view(|cx| {
Editor::auto_height(2, Some(|theme| theme.search.editor.input.clone()), cx)
Editor::auto_height(
2,
Some(Arc::new(|theme| theme.search.editor.input.clone())),
cx,
)
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();

View File

@@ -20,6 +20,7 @@ use std::{
any::{Any, TypeId},
ops::Range,
path::PathBuf,
sync::Arc,
};
use util::ResultExt as _;
use workspace::{
@@ -378,8 +379,10 @@ impl ProjectSearchView {
.detach();
let query_editor = cx.add_view(|cx| {
let mut editor =
Editor::single_line(Some(|theme| theme.search.editor.input.clone()), cx);
let mut editor = Editor::single_line(
Some(Arc::new(|theme| theme.search.editor.input.clone())),
cx,
);
editor.set_text(query_text, cx);
editor
});

View File

@@ -423,12 +423,14 @@ pub struct ChannelName {
pub name: TextStyle,
}
#[derive(Deserialize, Default)]
#[derive(Clone, Deserialize, Default)]
pub struct Picker {
#[serde(flatten)]
pub container: ContainerStyle,
pub empty: ContainedLabel,
pub empty_container: ContainerStyle,
pub input_editor: FieldEditor,
pub empty_input_editor: FieldEditor,
pub no_matches: ContainedLabel,
pub item: Interactive<ContainedLabel>,
}

View File

@@ -38,7 +38,7 @@ pub enum Event {
impl ThemeSelector {
fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
let handle = cx.weak_handle();
let picker = cx.add_view(|cx| Picker::new(handle, cx));
let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx));
let settings = cx.global::<Settings>();
let original_theme = settings.theme.clone();

View File

@@ -137,11 +137,6 @@ impl Motion {
)
}
pub fn infallible(self) -> bool {
use Motion::*;
matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
}
pub fn inclusive(self) -> bool {
use Motion::*;
match self {
@@ -169,9 +164,9 @@ impl Motion {
point: DisplayPoint,
goal: SelectionGoal,
times: usize,
) -> Option<(DisplayPoint, SelectionGoal)> {
) -> (DisplayPoint, SelectionGoal) {
use Motion::*;
let (new_point, goal) = match self {
match self {
Left => (left(map, point, times), SelectionGoal::None),
Backspace => (backspace(map, point, times), SelectionGoal::None),
Down => down(map, point, goal, times),
@@ -196,9 +191,7 @@ impl Motion {
StartOfDocument => (start_of_document(map, point, times), SelectionGoal::None),
EndOfDocument => (end_of_document(map, point, times), SelectionGoal::None),
Matching => (matching(map, point), SelectionGoal::None),
};
(new_point != point || self.infallible()).then_some((new_point, goal))
}
}
// Expands a selection using self motion for an operator
@@ -208,13 +201,12 @@ impl Motion {
selection: &mut Selection<DisplayPoint>,
times: usize,
expand_to_surrounding_newline: bool,
) -> bool {
if let Some((new_head, goal)) =
self.move_point(map, selection.head(), selection.goal, times)
{
selection.set_head(new_head, goal);
) {
let (new_head, goal) = self.move_point(map, selection.head(), selection.goal, times);
selection.set_head(new_head, goal);
if self.linewise() {
if self.linewise() {
if selection.start != selection.end {
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
if expand_to_surrounding_newline {
@@ -223,7 +215,7 @@ impl Motion {
*selection.end.column_mut() = 0;
selection.end = map.clip_point(selection.end, Bias::Right);
// Don't reset the end here
return true;
return;
} else if selection.start.row() > 0 {
*selection.start.row_mut() -= 1;
*selection.start.column_mut() = map.line_len(selection.start.row());
@@ -232,33 +224,31 @@ impl Motion {
}
(_, selection.end) = map.next_line_boundary(selection.end.to_point(map));
} else {
// If the motion is exclusive and the end of the motion is in column 1, the
// end of the motion is moved to the end of the previous line and the motion
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
// but "d}" will not include that line.
let mut inclusive = self.inclusive();
if !inclusive
&& self != Motion::Backspace
&& selection.end.row() > selection.start.row()
&& selection.end.column() == 0
{
inclusive = true;
*selection.end.row_mut() -= 1;
*selection.end.column_mut() = 0;
selection.end = map.clip_point(
map.next_line_boundary(selection.end.to_point(map)).1,
Bias::Left,
);
}
if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
*selection.end.column_mut() += 1;
}
}
true
} else {
false
// If the motion is exclusive and the end of the motion is in column 1, the
// end of the motion is moved to the end of the previous line and the motion
// becomes inclusive. Example: "}" moves to the first line after a paragraph,
// but "d}" will not include that line.
let mut inclusive = self.inclusive();
if !inclusive
&& self != Motion::Backspace
&& selection.end.row() > selection.start.row()
&& selection.end.column() == 0
&& selection.end.row() > 0
{
inclusive = true;
*selection.end.row_mut() -= 1;
*selection.end.column_mut() = 0;
selection.end = map.clip_point(
map.next_line_boundary(selection.end.to_point(map)).1,
Bias::Left,
);
}
if inclusive && selection.end.column() < map.line_len(selection.end.row()) {
*selection.end.column_mut() += 1;
}
}
}
}
@@ -266,7 +256,7 @@ impl Motion {
fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
for _ in 0..times {
*point.column_mut() = point.column().saturating_sub(1);
point = map.clip_point(point, Bias::Left);
point = map.clip_point(point, Bias::Right);
if point.column() == 0 {
break;
}
@@ -335,7 +325,9 @@ pub(crate) fn next_word_start(
|| at_newline && crossed_newline
|| at_newline && left == '\n'; // Prevents skipping repeated empty lines
crossed_newline |= at_newline;
if at_newline {
crossed_newline = true;
}
found
})
}
@@ -358,7 +350,7 @@ fn next_word_end(
});
// find_boundary clips, so if the character after the next character is a newline or at the end of the document, we know
// we have backtracked already
// we have backtraced already
if !map
.chars_at(point)
.nth(1)

View File

@@ -115,11 +115,7 @@ pub fn normal_object(object: Object, cx: &mut MutableAppContext) {
fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut MutableAppContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_cursors_with(|map, cursor, goal| {
motion
.move_point(map, cursor, goal, times)
.unwrap_or((cursor, goal))
})
s.move_cursors_with(|map, cursor, goal| motion.move_point(map, cursor, goal, times))
})
});
}
@@ -129,7 +125,7 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspa
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
s.move_cursors_with(|map, cursor, goal| {
Motion::Right.move_point(map, cursor, goal, 1)
});
});
@@ -146,7 +142,7 @@ fn insert_first_non_whitespace(
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
s.move_cursors_with(|map, cursor, goal| {
Motion::FirstNonWhitespace.move_point(map, cursor, goal, 1)
});
});
@@ -159,7 +155,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
vim.switch_mode(Mode::Insert, false, cx);
vim.update_active_editor(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
s.move_cursors_with(|map, cursor, goal| {
Motion::EndOfLine.move_point(map, cursor, goal, 1)
});
});
@@ -219,7 +215,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
(end_of_line..end_of_line, new_text)
});
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.maybe_move_cursors_with(|map, cursor, goal| {
s.move_cursors_with(|map, cursor, goal| {
Motion::EndOfLine.move_point(map, cursor, goal, 1)
});
});
@@ -851,10 +847,4 @@ mod test {
.await;
}
}
#[gpui::test]
async fn test_h_through_unicode(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["h"]);
cx.assert_all("Testˇ├ˇ──ˇ┐ˇTest").await;
}
}

View File

@@ -1,40 +1,27 @@
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
use editor::{
char_kind, display_map::DisplaySnapshot, movement, Autoscroll, CharKind, DisplayPoint,
};
use editor::{char_kind, display_map::DisplaySnapshot, movement, Autoscroll, DisplayPoint};
use gpui::MutableAppContext;
use language::Selection;
pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut MutableAppContext) {
// Some motions ignore failure when switching to normal mode
let mut motion_succeeded = matches!(
motion,
Motion::Left | Motion::Right | Motion::EndOfLine | Motion::Backspace | Motion::StartOfLine
);
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
// We are swapping to insert mode anyway. Just set the line end clipping behavior now
editor.set_clip_at_line_ends(false, cx);
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_with(|map, selection| {
motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion
{
expand_changed_word_selection(map, selection, times, ignore_punctuation)
if let Motion::NextWordStart { ignore_punctuation } = motion {
expand_changed_word_selection(map, selection, times, ignore_punctuation);
} else {
motion.expand_selection(map, selection, times, false)
};
motion.expand_selection(map, selection, times, false);
}
});
});
copy_selections_content(editor, motion.linewise(), cx);
editor.insert("", cx);
});
});
if motion_succeeded {
vim.switch_mode(Mode::Insert, false, cx)
} else {
vim.switch_mode(Mode::Normal, false, cx)
}
vim.switch_mode(Mode::Insert, false, cx)
}
pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut MutableAppContext) {
@@ -62,45 +49,36 @@ pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Mutab
}
}
// From the docs https://vimdoc.sourceforge.net/htmldoc/motion.html
// Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
// on a non-blank. This is because "cw" is interpreted as change-word, and a
// word does not include the following white space. {Vi: "cw" when on a blank
// followed by other blanks changes only the first blank; this is probably a
// bug, because "dw" deletes all the blanks}
//
// NOT HANDLED YET
// Another special case: When using the "w" motion in combination with an
// operator and the last word moved over is at the end of a line, the end of
// that word becomes the end of the operated text, not the first word in the
// next line.
// From the docs https://vimhelp.org/change.txt.html#cw
// Special case: When the cursor is in a word, "cw" and "cW" do not include the
// white space after a word, they only change up to the end of the word. This is
// because Vim interprets "cw" as change-word, and a word does not include the
// following white space.
fn expand_changed_word_selection(
map: &DisplaySnapshot,
selection: &mut Selection<DisplayPoint>,
times: usize,
ignore_punctuation: bool,
) -> bool {
if times == 1 {
let in_word = map
.chars_at(selection.head())
.next()
.map(|(c, _)| char_kind(c) != CharKind::Whitespace)
.unwrap_or_default();
if in_word {
selection.end = movement::find_boundary(map, selection.end, |left, right| {
let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation);
let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation);
left_kind != right_kind && left_kind != CharKind::Whitespace
});
true
} else {
Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, 1, false)
}
} else {
Motion::NextWordStart { ignore_punctuation }.expand_selection(map, selection, times, false)
) {
if times > 1 {
Motion::NextWordStart { ignore_punctuation }.expand_selection(
map,
selection,
times - 1,
false,
);
}
if times == 1 && selection.end.column() == map.line_len(selection.end.row()) {
return;
}
selection.end = movement::find_boundary(map, selection.end, |left, right| {
let left_kind = char_kind(left).coerce_punctuation(ignore_punctuation);
let right_kind = char_kind(right).coerce_punctuation(ignore_punctuation);
left_kind != right_kind || left == '\n' || right == '\n'
});
}
#[cfg(test)]

View File

@@ -143,7 +143,7 @@ mod test {
Test test
ˇ
test"},
ExemptionFeatures::DeleteWordOnEmptyLine,
ExemptionFeatures::DeletionOnEmptyLine,
)
.await;
@@ -169,7 +169,7 @@ mod test {
Test test
ˇ
test"},
ExemptionFeatures::OperatorLastNewlineRemains,
ExemptionFeatures::DeletionOnEmptyLine,
)
.await;

View File

@@ -8,10 +8,7 @@ use util::test::marked_text_offsets;
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
use crate::state::Mode;
pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[
ExemptionFeatures::DeletionOnEmptyLine,
ExemptionFeatures::OperatorAbortsOnFailedMotion,
];
pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[];
/// Enum representing features we have tests for but which don't work, yet. Used
/// to add exemptions and automatically
@@ -22,10 +19,6 @@ pub enum ExemptionFeatures {
DeletionOnEmptyLine,
// When a motion fails, it should should not apply linewise operations
OperatorAbortsOnFailedMotion,
// When an operator completes at the end of the file, an extra newline is left
OperatorLastNewlineRemains,
// Deleting a word on an empty line doesn't remove the newline
DeleteWordOnEmptyLine,
// OBJECTS
// Resulting position after the operation is slightly incorrect for unintuitive reasons.

View File

@@ -30,23 +30,20 @@ pub fn visual_motion(motion: Motion, times: usize, cx: &mut MutableAppContext) {
s.move_with(|map, selection| {
let was_reversed = selection.reversed;
if let Some((new_head, goal)) =
motion.move_point(map, selection.head(), selection.goal, times)
{
selection.set_head(new_head, goal);
let (new_head, goal) =
motion.move_point(map, selection.head(), selection.goal, times);
selection.set_head(new_head, goal);
if was_reversed && !selection.reversed {
// Head was at the start of the selection, and now is at the end. We need to move the start
// back by one if possible in order to compensate for this change.
*selection.start.column_mut() =
selection.start.column().saturating_sub(1);
selection.start = map.clip_point(selection.start, Bias::Left);
} else if !was_reversed && selection.reversed {
// Head was at the end of the selection, and now is at the start. We need to move the end
// forward by one if possible in order to compensate for this change.
*selection.end.column_mut() = selection.end.column() + 1;
selection.end = map.clip_point(selection.end, Bias::Right);
}
if was_reversed && !selection.reversed {
// Head was at the start of the selection, and now is at the end. We need to move the start
// back by one if possible in order to compensate for this change.
*selection.start.column_mut() = selection.start.column().saturating_sub(1);
selection.start = map.clip_point(selection.start, Bias::Left);
} else if !was_reversed && selection.reversed {
// Head was at the end of the selection, and now is at the start. We need to move the end
// forward by one if possible in order to compensate for this change.
*selection.end.column_mut() = selection.end.column() + 1;
selection.end = map.clip_point(selection.end, Bias::Right);
}
});
});

View File

@@ -1 +1 @@
[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over\n"},{"Mode":"Insert"},{"Selection":{"start":[3,0],"end":[3,0]}},{"Mode":"Insert"}]
[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}]

View File

@@ -1 +1 @@
[{"Text":"\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nbrown fox\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"\nbrown fox\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}]
[{"Text":"\njumps over\nthe lazy"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":""},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}]

View File

@@ -1 +1 @@
[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,6],"end":[2,6]}},{"Mode":"Normal"},{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\n"},{"Mode":"Normal"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Normal"}]
[{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"}]

View File

@@ -1 +1 @@
[{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}]
[{"Text":"\njumps over"},{"Mode":"Insert"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Insert"},{"Text":"The quick\n"},{"Mode":"Insert"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Insert"}]

View File

@@ -1 +1 @@
[{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}]
[{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox"},{"Mode":"Normal"},{"Selection":{"start":[1,6],"end":[1,6]}},{"Mode":"Normal"}]

View File

@@ -1 +1 @@
[{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,5],"end":[2,5]}},{"Mode":"Normal"},{"Text":"The quick\nbrown fox\njumps over"},{"Mode":"Normal"},{"Selection":{"start":[2,0],"end":[2,0]}},{"Mode":"Normal"}]
[{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"The quick"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"}]

View File

@@ -1 +1 @@
[{"Text":"jumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":"brown fox\njumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}]
[{"Text":"jumps over\nthe lazy"},{"Mode":"Normal"},{"Selection":{"start":[0,5],"end":[0,5]}},{"Mode":"Normal"},{"Text":""},{"Mode":"Normal"},{"Selection":{"start":[0,0],"end":[0,0]}},{"Mode":"Normal"}]

View File

@@ -1 +0,0 @@
[{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,3],"end":[0,3]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,4],"end":[0,4]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,10],"end":[0,10]}},{"Mode":"Normal"},{"Text":"Test├──┐Test"},{"Mode":"Normal"},{"Selection":{"start":[0,13],"end":[0,13]}},{"Mode":"Normal"}]

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.63.2"
version = "0.64.0"
[lib]
name = "zed"
@@ -102,6 +102,7 @@ tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown",
tree-sitter-python = "0.20.2"
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
tree-sitter-typescript = "0.20.1"
tree-sitter-ruby = "0.20.0"
tree-sitter-html = "0.19.0"
url = "2.2"

View File

@@ -1 +1 @@
preview
dev

View File

@@ -15,6 +15,15 @@ mod python;
mod rust;
mod typescript;
// 1. Add tree-sitter-{language} parser to zed crate
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
// 3. Add config.toml to the newly created language directory using existing languages as a template
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
// Note: github highlights take the last match while zed takes the first
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
// and autoclosing brackets respectively
// 6. If the language has injections add an injections.scm query file
#[derive(RustEmbed)]
#[folder = "src/languages"]
#[exclude = "*.rs"]
@@ -107,6 +116,7 @@ pub async fn init(languages: Arc<LanguageRegistry>, _executor: Arc<Background>)
tree_sitter_html::language(),
Some(CachedLspAdapter::new(html::HtmlLspAdapter).await),
),
("ruby", tree_sitter_ruby::language(), None),
] {
languages.add(language(name, grammar, lsp_adapter));
}

View File

@@ -0,0 +1,14 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
("do" @open "end" @close)
(block_parameters "|" @open "|" @close)
(interpolation "#{" @open "}" @close)
(if "if" @open "end" @close)
(unless "unless" @open "end" @close)
(begin "begin" @open "end" @close)
(module "module" @open "end" @close)
(_ . "def" @open "end" @close)
(_ . "class" @open "end" @close)

View File

@@ -0,0 +1,11 @@
name = "Ruby"
path_suffixes = ["rb", "Gemfile"]
line_comment = "# "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false },
{ start = "'", end = "'", close = false, newline = false },
]

View File

@@ -0,0 +1,181 @@
; Keywords
[
"alias"
"and"
"begin"
"break"
"case"
"class"
"def"
"do"
"else"
"elsif"
"end"
"ensure"
"for"
"if"
"in"
"module"
"next"
"or"
"rescue"
"retry"
"return"
"then"
"unless"
"until"
"when"
"while"
"yield"
] @keyword
(identifier) @variable
((identifier) @keyword
(#match? @keyword "^(private|protected|public)$"))
; Function calls
((identifier) @function.method.builtin
(#eq? @function.method.builtin "require"))
"defined?" @function.method.builtin
(call
method: [(identifier) (constant)] @function.method)
; Function definitions
(alias (identifier) @function.method)
(setter (identifier) @function.method)
(method name: [(identifier) (constant)] @function.method)
(singleton_method name: [(identifier) (constant)] @function.method)
; Identifiers
[
(class_variable)
(instance_variable)
] @property
((identifier) @constant.builtin
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
(file) @constant.builtin
(line) @constant.builtin
(encoding) @constant.builtin
(hash_splat_nil
"**" @operator
) @constant.builtin
((constant) @constant
(#match? @constant "^[A-Z\\d_]+$"))
(constant) @type
(self) @variable.special
(super) @variable.special
; Literals
[
(string)
(bare_string)
(subshell)
(heredoc_body)
(heredoc_beginning)
] @string
[
(simple_symbol)
(delimited_symbol)
(hash_key_symbol)
(bare_symbol)
] @string.special.symbol
(regex) @string.special.regex
(escape_sequence) @escape
[
(integer)
(float)
] @number
[
(nil)
(true)
(false)
] @constant.builtin
(interpolation
"#{" @punctuation.special
"}" @punctuation.special) @embedded
(comment) @comment
; Operators
[
"!"
"~"
"+"
"-"
"**"
"*"
"/"
"%"
"<<"
">>"
"&"
"|"
"^"
">"
"<"
"<="
">="
"=="
"!="
"=~"
"!~"
"<=>"
"||"
"&&"
".."
"..."
"="
"**="
"*="
"/="
"%="
"+="
"-="
"<<="
">>="
"&&="
"&="
"||="
"|="
"^="
"=>"
"->"
(operator)
] @operator
[
","
";"
"."
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
"%w("
"%i("
] @punctuation.bracket

View File

@@ -0,0 +1,17 @@
(method "end" @end) @indent
(class "end" @end) @indent
(module "end" @end) @indent
(begin "end" @end) @indent
(do_block "end" @end) @indent
(then) @indent
(call) @indent
(ensure) @outdent
(rescue) @outdent
(else) @outdent
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View File

@@ -0,0 +1,11 @@
(class
"class" @context
name: (_) @name) @item
(method
"def" @context
name: (_) @name) @item
(module
"module" @context
name: (_) @name) @item

View File

@@ -76,7 +76,7 @@ lazy_static! {
pub static ref RELEASE_CHANNEL: ReleaseChannel = match RELEASE_CHANNEL_NAME.as_str() {
"dev" => ReleaseChannel::Dev,
"preview" => ReleaseChannel::Preview,
"stable" => ReleaseChannel::Preview,
"stable" => ReleaseChannel::Stable,
_ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME),
};
}

View File

@@ -16,6 +16,7 @@ if [[ $(git rev-parse --abbrev-ref HEAD) != "main" ]]; then
fi
git pull -q --ff-only origin main
git fetch --tags
cargo check -q
# Parse the current version
version=$(script/get-crate-version zed)
@@ -75,6 +76,7 @@ git commit -q --all --message "${prev_minor_branch_name} stable"
git tag ${stable_tag_name}
echo "Creating new preview branch ${minor_branch_name}..."
git checkout -q main
git checkout -q -b ${minor_branch_name}
echo -n preview > crates/zed/RELEASE_CHANNEL
git commit -q --all --message "${minor_branch_name} preview"

View File

@@ -15,4 +15,4 @@ case $channel in
;;
esac
exec script/lib/bump-version.sh zed v $tag_suffix patch
exec script/lib/bump-version.sh zed v "$tag_suffix" patch

View File

@@ -8,14 +8,14 @@ const FIXES_REGEX = /(fixes|closes) (.+[/#]\d+.*)$/im;
main();
async function main() {
// Get the last two tags
// Get the last two preview tags
const [newTag, oldTag] = execFileSync(
"git",
["tag", "--sort", "-committerdate"],
{ encoding: "utf8" }
)
.split("\n")
.filter((t) => t.startsWith("v"));
.filter((t) => t.startsWith("v") && t.endsWith('-pre'));
// Print the previous release
console.log(`Changes from ${oldTag} to ${newTag}\n`);
@@ -49,9 +49,28 @@ async function main() {
.filter((line) => line.length > 0)
.map((line) => line.match(PR_REGEX)[1]);
// Get the PRs that were cherry-picked between main and the old tag.
const existingPullRequestNumbers = new Set(execFileSync(
"git",
[
"log",
`main..${oldTag}`,
"--oneline",
"--grep",
"Merge pull request",
],
{ encoding: "utf8" }
)
.split("\n")
.filter((line) => line.length > 0)
.map((line) => line.match(PR_REGEX)[1]));
// Filter out those existing PRs from the set of new PRs.
const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number));
// Fetch the pull requests from the GitHub API.
console.log("Merged Pull requests:")
for (const pullRequestNumber of pullRequestNumbers) {
for (const pullRequestNumber of newPullRequestNumbers) {
const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`;
const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`;

View File

@@ -24,7 +24,9 @@ job_image_ids=$(
kubectl \
--namespace=${environment} \
get jobs \
-o 'jsonpath={range .items[0:5]}{.spec.template.spec.containers[0].image}{"\n"}{end}'
-o 'jsonpath={range .items[0:5]}{.spec.template.spec.containers[0].image}{"\n"}{end}' \
2> /dev/null \
|| true
)
echo "Deployed image version:"

View File

@@ -3,7 +3,7 @@ import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, foreground, text } from "./components";
export default function contactFinder(colorScheme: ColorScheme) {
let layer = colorScheme.highest;
let layer = colorScheme.middle;
const sideMargin = 6;
const contactButton = {
@@ -14,31 +14,36 @@ export default function contactFinder(colorScheme: ColorScheme) {
cornerRadius: 8,
};
const pickerStyle = picker(colorScheme);
const pickerInput = {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono",),
placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: sideMargin,
right: sideMargin,
}
};
return {
picker: {
emptyContainer: {},
item: {
...picker(colorScheme).item,
margin: { left: sideMargin, right: sideMargin }
...pickerStyle.item,
margin: { left: sideMargin, right: sideMargin },
},
empty: picker(colorScheme).empty,
inputEditor: {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono",),
placeholderText: text(layer, "mono", "variant", { size: "sm" }),
selection: colorScheme.players[0],
border: border(layer),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: sideMargin,
right: sideMargin,
}
}
noMatches: pickerStyle.noMatches,
inputEditor: pickerInput,
emptyInputEditor: pickerInput
},
rowHeight: 28,
contactAvatar: {

View File

@@ -53,7 +53,7 @@ export default function contactsPanel(colorScheme: ColorScheme) {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono", "on"),
placeholderText: text(layer, "mono", "on", "disabled", { size: "sm" }),
placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer, "on"),
padding: {

View File

@@ -1,10 +1,6 @@
import { fontWeights } from "../common";
import { withOpacity } from "../utils/color";
import {
ColorScheme,
Layer,
StyleSets,
} from "../themes/common/colorScheme";
import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme";
import {
background,
border,
@@ -36,8 +32,8 @@ export default function editor(colorScheme: ColorScheme) {
}),
},
message: {
text: text(layer, "sans", styleSet, "default", { size: "sm" }),
highlightText: text(layer, "sans", styleSet, "default", {
text: text(layer, "sans", styleSet, "inverted", { size: "sm" }),
highlightText: text(layer, "sans", styleSet, "inverted", {
size: "sm",
weight: "bold",
}),
@@ -50,6 +46,11 @@ export default function editor(colorScheme: ColorScheme) {
color: colorScheme.ramps.neutral(1).hex(),
weight: fontWeights.normal,
},
"variable.special": {
// Highlights for self, this, etc
color: colorScheme.ramps.blue(0.7).hex(),
weight: fontWeights.normal,
},
comment: {
color: colorScheme.ramps.neutral(0.71).hex(),
weight: fontWeights.normal,
@@ -270,9 +271,9 @@ export default function editor(colorScheme: ColorScheme) {
background: withOpacity(background(layer, "inverted"), 0.4),
border: {
width: 1,
color: borderColor(layer, 'variant'),
color: borderColor(layer, "variant"),
},
}
},
},
compositionMark: {
underline: {

View File

@@ -3,13 +3,39 @@ import { background, border, text } from "./components";
export default function picker(colorScheme: ColorScheme) {
let layer = colorScheme.lowest;
return {
const container = {
background: background(layer),
border: border(layer),
shadow: colorScheme.modalShadow,
cornerRadius: 12,
padding: {
bottom: 4,
}
};
const inputEditor = {
placeholderText: text(layer, "sans", "on", "disabled"),
selection: colorScheme.players[0],
text: text(layer, "mono", "on"),
border: border(layer, { bottom: true }),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
margin: {
bottom: 4,
},
};
const emptyInputEditor = { ...inputEditor };
delete emptyInputEditor.border;
delete emptyInputEditor.margin;
return {
...container,
emptyContainer: {
...container,
padding: {}
},
item: {
padding: {
@@ -37,7 +63,9 @@ export default function picker(colorScheme: ColorScheme) {
background: background(layer, "hovered"),
},
},
empty: {
inputEditor,
emptyInputEditor,
noMatches: {
text: text(layer, "sans", "variant"),
padding: {
bottom: 8,
@@ -46,20 +74,5 @@ export default function picker(colorScheme: ColorScheme) {
top: 8,
},
},
inputEditor: {
placeholderText: text(layer, "sans", "on", "disabled"),
selection: colorScheme.players[0],
text: text(layer, "mono", "on"),
border: border(layer, { bottom: true }),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
margin: {
bottom: 4,
},
},
};
}

View File

@@ -0,0 +1,31 @@
import chroma from "chroma-js";
import { colorRamp, createColorScheme } from "../common/ramps";
const name = "Abruzzo";
const author = "slightknack <hey@isaac.sh>";
const url = "https://github.com/slightknack";
const license = {
type: "",
url: ""
}
export const dark = createColorScheme(`${name}`, false, {
neutral: chroma.scale([
"#1b0d05",
"#2c1e18",
"#654035",
"#9d5e4a",
"#b37354",
"#c1825a",
"#dda66e",
"#fbf3e2",
]),
red: colorRamp(chroma("#e594c4")),
orange: colorRamp(chroma("#d9e87e")),
yellow: colorRamp(chroma("#fd9d83")),
green: colorRamp(chroma("#96adf7")),
cyan: colorRamp(chroma("#fc798f")),
blue: colorRamp(chroma("#BCD0F5")),
violet: colorRamp(chroma("#dac5eb")),
magenta: colorRamp(chroma("#c1a3ef")),
});