Compare commits
26 Commits
v0.63.2-pr
...
collab-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ff459014 | ||
|
|
927cfa44db | ||
|
|
7301ab4f44 | ||
|
|
0b231e58fd | ||
|
|
08b84416d2 | ||
|
|
aec8aec800 | ||
|
|
5bcf9916c9 | ||
|
|
6076a3fc61 | ||
|
|
05389dc239 | ||
|
|
d222904471 | ||
|
|
9f3ea0c87f | ||
|
|
601ec40ddc | ||
|
|
adc4a5984e | ||
|
|
0f78174d78 | ||
|
|
ad67a1b744 | ||
|
|
edc2966651 | ||
|
|
eacfa856cf | ||
|
|
fe4862d756 | ||
|
|
8312d974ac | ||
|
|
6d3bd495fc | ||
|
|
576e350bea | ||
|
|
cc1325d6f9 | ||
|
|
c411cb7eef | ||
|
|
c9ba41d002 | ||
|
|
3caa7a4916 | ||
|
|
4f344f1ac1 |
8
.github/workflows/release_actions.yml
vendored
8
.github/workflows/release_actions.yml
vendored
@@ -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
17
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 . .
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -71,4 +71,5 @@ tree-sitter-json = "*"
|
||||
tree-sitter-rust = "*"
|
||||
tree-sitter-python = "*"
|
||||
tree-sitter-typescript = "*"
|
||||
tree-sitter-ruby = "*"
|
||||
unindent = "0.1.7"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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"}]
|
||||
@@ -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
@@ -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"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
preview
|
||||
dev
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
14
crates/zed/src/languages/ruby/brackets.scm
Normal file
14
crates/zed/src/languages/ruby/brackets.scm
Normal 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)
|
||||
11
crates/zed/src/languages/ruby/config.toml
Normal file
11
crates/zed/src/languages/ruby/config.toml
Normal 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 },
|
||||
]
|
||||
181
crates/zed/src/languages/ruby/highlights.scm
Normal file
181
crates/zed/src/languages/ruby/highlights.scm
Normal 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
|
||||
17
crates/zed/src/languages/ruby/indents.scm
Normal file
17
crates/zed/src/languages/ruby/indents.scm
Normal 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
|
||||
11
crates/zed/src/languages/ruby/outline.scm
Normal file
11
crates/zed/src/languages/ruby/outline.scm
Normal file
@@ -0,0 +1,11 @@
|
||||
(class
|
||||
"class" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(method
|
||||
"def" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(module
|
||||
"module" @context
|
||||
name: (_) @name) @item
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
31
styles/src/themes/experiments/abruzzo.ts
Normal file
31
styles/src/themes/experiments/abruzzo.ts
Normal 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")),
|
||||
});
|
||||
Reference in New Issue
Block a user