Merge remote-tracking branch 'origin/main' into tokio

This commit is contained in:
Nathan Sobo
2022-04-26 11:16:35 -06:00
70 changed files with 12350 additions and 1039 deletions

3
Cargo.lock generated
View File

@@ -2588,6 +2588,7 @@ dependencies = [
"tree-sitter",
"tree-sitter-json 0.19.0",
"tree-sitter-rust",
"tree-sitter-typescript",
"unindent",
"util",
]
@@ -5857,7 +5858,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "zed"
version = "0.29.0"
version = "0.30.0"
dependencies = [
"anyhow",
"assets",

View File

@@ -1,6 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.87348 15.1266C4.04217 12.2953 4.04217 7.70484 6.87348 4.87354M17.1265 4.87354C19.9578 7.70484 19.9578 12.2953 17.1265 15.1266" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.9948 13.0052C7.33507 11.3454 7.33507 8.65448 8.9948 6.99475M15.0052 6.99475C16.6649 8.65448 16.6649 11.3454 15.0052 13.0052" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5 10C12.5 10.2761 12.2761 10.5 12 10.5C11.7239 10.5 11.5 10.2761 11.5 10C11.5 9.72386 11.7239 9.5 12 9.5C12.2761 9.5 12.5 9.72386 12.5 10Z" stroke="#636B78" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13.75V19.25" stroke="#636B78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 879 B

3
assets/icons/share.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.0002 0.333344C2.77885 0.333344 0.16687 2.94532 0.16687 6.16668C0.16687 8.56824 1.61921 10.6302 3.69291 11.5234C3.60229 10.9831 3.53223 10.4404 3.51062 10.0091C2.25203 9.19011 1.41661 7.77605 1.41661 6.16668C1.41661 3.63933 3.4726 1.58334 5.99994 1.58334C8.52729 1.58334 10.5833 3.63933 10.5833 6.16668C10.5833 7.77579 9.74786 9.19011 8.48979 10.0104C8.46791 10.4401 8.39734 10.9836 8.30645 11.5253C10.38 10.6302 11.8331 8.56772 11.8331 6.16668C11.8331 2.94532 9.22117 0.333344 5.99981 0.333344H6.0002ZM6.0002 8.45834C5.14395 8.45834 4.33354 8.68295 4.33354 9.59767C4.33354 10.4604 4.66895 12.3138 4.87052 13.056C5.00541 13.5521 5.50802 13.6667 6.0002 13.6667C6.49239 13.6667 6.9963 13.5527 7.12989 13.0578C7.33093 12.3099 7.66687 10.4583 7.66687 9.59897C7.66687 8.6823 6.85697 8.45834 6.0002 8.45834ZM6.0002 7.62501C6.80619 7.62501 7.45854 6.97267 7.45854 6.16668C7.45854 5.36069 6.78406 4.70834 6.0002 4.70834C5.21635 4.70834 4.54187 5.35939 4.54187 6.16668C4.54187 6.97397 5.19291 7.62501 6.0002 7.62501ZM9.7502 6.16668C9.7502 4.09558 8.0713 2.41668 6.0002 2.41668C3.92911 2.41668 2.2502 4.09636 2.2502 6.16668C2.2502 7.33413 2.79499 8.36407 3.63145 9.05209C3.75776 8.72267 3.99942 8.37814 4.46609 8.13959C4.46635 8.13803 4.47416 8.13803 4.47937 8.13803C3.88822 7.6797 3.5002 6.97136 3.5002 6.16668C3.5002 4.78595 4.61947 3.66668 6.0002 3.66668C7.38093 3.66668 8.5002 4.78595 8.5002 6.16668C8.5002 6.97189 8.11296 7.68048 7.52182 8.13751C7.5252 8.13803 7.53484 8.13855 7.53406 8.13933C8.00098 8.37788 8.24213 8.72215 8.36869 9.05183C9.20567 8.36433 9.74994 7.33308 9.74994 6.16642L9.7502 6.16668Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -210,9 +210,11 @@
"cmd-shift-F": "project_search::Deploy",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k t": "theme_selector::Reload",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"cmd-shift-P": "command_palette::Toggle"
"cmd-shift-P": "command_palette::Toggle",
"cmd-shift-M": "diagnostics::Deploy"
}
},
// Bindings from Sublime Text
@@ -244,6 +246,7 @@
"cmd-k cmd-right": "workspace::ActivateNextPane"
}
},
// Bindings from Atom
{
"context": "Pane",
"bindings": {
@@ -289,18 +292,13 @@
{
"bindings": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
"cmd-alt-i": "zed::DebugElements",
"alt-cmd-,": "zed::OpenKeymap"
"cmd-alt-i": "zed::DebugElements"
}
},
{
"context": "Editor",
"bindings": {
"ctrl-w": "editor::SelectLargerSyntaxNode",
"ctrl-shift-W": "editor::SelectSmallerSyntaxNode",
"alt-cmd-f": "editor::FoldSelectedRanges",
"alt-enter": "editor::OpenExcerpts",
"cmd-f10": "editor::RestartLanguageServer"
"alt-enter": "editor::OpenExcerpts"
}
},
{
@@ -312,8 +310,6 @@
{
"context": "Workspace",
"bindings": {
"alt-shift-D": "diagnostics::Deploy",
"ctrl-alt-cmd-j": "journal::NewJournalEntry",
"cmd-1": [
"workspace::ToggleSidebarItemFocus",
{

View File

@@ -75,37 +75,13 @@
{
"context": "Editor && vim_operator == c",
"bindings": {
"w": [
"vim::NextWordEnd",
{
"ignorePunctuation": false
}
],
"w": "vim::ChangeWord",
"shift-W": [
"vim::NextWordEnd",
"vim::ChangeWord",
{
"ignorePunctuation": true
}
]
}
},
{
"context": "Editor && vim_operator == d",
"bindings": {
"w": [
"vim::NextWordStart",
{
"ignorePunctuation": false,
"stopAtNewline": true
}
],
"shift-W": [
"vim::NextWordStart",
{
"ignorePunctuation": true,
"stopAtNewline": true
}
]
}
}
]

1435
assets/themes/cave-dark.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -89,10 +89,6 @@
"top": 7
}
},
"margin": {
"bottom": 52,
"top": 52
},
"shadow": {
"blur": 16,
"color": "#00000052",
@@ -158,6 +154,13 @@
"right": 8
}
},
"modal": {
"margin": {
"bottom": 52,
"top": 52
},
"cursor": "Arrow"
},
"left_sidebar": {
"width": 30,
"background": "#1c1c1c",
@@ -255,8 +258,9 @@
"avatar_width": 18,
"height": 32,
"background": "#2b2b2b",
"share_icon_color": "#9c9c9c",
"share_icon_active_color": "#2472f2",
"padding": {
"left": 80
},
"title": {
"family": "Zed Sans",
"color": "#f1f1f1",
@@ -321,10 +325,48 @@
"right": 4
}
},
"share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"color": "#9c9c9c"
},
"hovered_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#323232",
"color": "#9c9c9c"
},
"hovered_active_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#323232",
"color": "#ffffff"
},
"active_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#1c1c1c",
"color": "#ffffff"
},
"outdated_warning": {
"family": "Zed Sans",
"color": "#f7bb57",
"size": 13
"size": 13,
"margin": {
"right": 6
}
}
},
"toolbar": {
@@ -689,33 +731,88 @@
}
},
"syntax": {
"keyword": "#4f8ff7",
"function": "#f9da82",
"string": "#f99d5f",
"type": "#3eeeda",
"number": "#aeef4b",
"comment": "#aaaaaa",
"property": "#4f8ff7",
"variant": "#53c1f5",
"constant": "#d5d5d5",
"primary": {
"color": "#d5d5d5",
"weight": "normal"
},
"comment": {
"color": "#aaaaaa",
"weight": "normal"
},
"punctuation": {
"color": "#c6c6c6",
"weight": "normal"
},
"constant": {
"color": "#d5d5d5",
"weight": "normal"
},
"keyword": {
"color": "#4f8ff7",
"weight": "normal"
},
"function": {
"color": "#f9da82",
"weight": "normal"
},
"type": {
"color": "#3eeeda",
"weight": "normal"
},
"variant": {
"color": "#53c1f5",
"weight": "normal"
},
"property": {
"color": "#4f8ff7",
"weight": "normal"
},
"enum": {
"color": "#ee670a",
"weight": "normal"
},
"operator": {
"color": "#ee670a",
"weight": "normal"
},
"string": {
"color": "#f99d5f",
"weight": "normal"
},
"number": {
"color": "#aeef4b",
"weight": "normal"
},
"boolean": {
"color": "#aeef4b",
"weight": "normal"
},
"predictive": {
"color": "#808080",
"weight": "normal"
},
"title": {
"color": "#de900c",
"weight": "bold"
},
"emphasis": "#4f8ff7",
"emphasis_strong": {
"emphasis": {
"color": "#4f8ff7",
"weight": "normal"
},
"emphasis.strong": {
"color": "#4f8ff7",
"weight": "bold"
},
"link_uri": {
"color": "#79ba16",
"weight": "normal",
"underline": true
},
"link_text": {
"color": "#ee670a",
"weight": "normal",
"italic": true
},
"list_marker": "#c6c6c6"
}
}
},
"project_diagnostics": {

View File

@@ -89,10 +89,6 @@
"top": 7
}
},
"margin": {
"bottom": 52,
"top": 52
},
"shadow": {
"blur": 16,
"color": "#0000001f",
@@ -158,6 +154,13 @@
"right": 8
}
},
"modal": {
"margin": {
"bottom": 52,
"top": 52
},
"cursor": "Arrow"
},
"left_sidebar": {
"width": 30,
"background": "#f8f8f8",
@@ -255,8 +258,9 @@
"avatar_width": 18,
"height": 32,
"background": "#eaeaea",
"share_icon_color": "#717171",
"share_icon_active_color": "#484bed",
"padding": {
"left": 80
},
"title": {
"family": "Zed Sans",
"color": "#2b2b2b",
@@ -321,10 +325,48 @@
"right": 4
}
},
"share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"color": "#717171"
},
"hovered_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#e3e3e3",
"color": "#717171"
},
"hovered_active_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#e3e3e3",
"color": "#000000"
},
"active_share_icon": {
"margin": {
"top": 3,
"bottom": 2
},
"corner_radius": 6,
"background": "#d5d5d5",
"color": "#000000"
},
"outdated_warning": {
"family": "Zed Sans",
"color": "#d3a20b",
"size": 13
"size": 13,
"margin": {
"right": 6
}
}
},
"toolbar": {
@@ -689,33 +731,88 @@
}
},
"syntax": {
"keyword": "#1819a1",
"function": "#bb550e",
"string": "#eb2d2d",
"type": "#a8820e",
"number": "#484bed",
"comment": "#717171",
"property": "#106c4e",
"variant": "#97142a",
"constant": "#1c1c1c",
"primary": {
"color": "#1c1c1c",
"weight": "normal"
},
"comment": {
"color": "#717171",
"weight": "normal"
},
"punctuation": {
"color": "#555555",
"weight": "normal"
},
"constant": {
"color": "#1c1c1c",
"weight": "normal"
},
"keyword": {
"color": "#1819a1",
"weight": "normal"
},
"function": {
"color": "#bb550e",
"weight": "normal"
},
"type": {
"color": "#a8820e",
"weight": "normal"
},
"variant": {
"color": "#97142a",
"weight": "normal"
},
"property": {
"color": "#106c4e",
"weight": "normal"
},
"enum": {
"color": "#eb2d2d",
"weight": "normal"
},
"operator": {
"color": "#eb2d2d",
"weight": "normal"
},
"string": {
"color": "#eb2d2d",
"weight": "normal"
},
"number": {
"color": "#484bed",
"weight": "normal"
},
"boolean": {
"color": "#eb2d2d",
"weight": "normal"
},
"predictive": {
"color": "#808080",
"weight": "normal"
},
"title": {
"color": "#1096d3",
"weight": "bold"
},
"emphasis": "#484bed",
"emphasis_strong": {
"emphasis": {
"color": "#484bed",
"weight": "normal"
},
"emphasis.strong": {
"color": "#484bed",
"weight": "bold"
},
"link_uri": {
"color": "#79ba16",
"weight": "normal",
"underline": true
},
"link_text": {
"color": "#eb2d2d",
"weight": "normal",
"italic": true
},
"list_marker": "#555555"
}
}
},
"project_diagnostics": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -814,14 +814,20 @@ pub mod tests {
DisplayPoint::new(0, 7)
);
assert_eq!(
movement::up(&snapshot, DisplayPoint::new(1, 10), SelectionGoal::None),
movement::up(
&snapshot,
DisplayPoint::new(1, 10),
SelectionGoal::None,
false
),
(DisplayPoint::new(0, 7), SelectionGoal::Column(10))
);
assert_eq!(
movement::down(
&snapshot,
DisplayPoint::new(0, 7),
SelectionGoal::Column(10)
SelectionGoal::Column(10),
false
),
(DisplayPoint::new(1, 10), SelectionGoal::Column(10))
);
@@ -829,7 +835,8 @@ pub mod tests {
movement::down(
&snapshot,
DisplayPoint::new(1, 10),
SelectionGoal::Column(10)
SelectionGoal::Column(10),
false
),
(DisplayPoint::new(2, 4), SelectionGoal::Column(10))
);

View File

@@ -1134,8 +1134,10 @@ impl Editor {
}
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
self.display_map
.update(cx, |map, _| map.clip_at_line_ends = clip);
if self.display_map.read(cx).clip_at_line_ends != clip {
self.display_map
.update(cx, |map, _| map.clip_at_line_ends = clip);
}
}
pub fn set_keymap_context_layer<Tag: 'static>(&mut self, context: gpui::keymap::Context) {
@@ -3579,13 +3581,13 @@ impl Editor {
if !selection.is_empty() {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) = movement::up(&map, selection.start, selection.goal);
let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false);
selection.collapse_to(cursor, goal);
});
}
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
self.move_selection_heads(cx, movement::up)
self.move_selection_heads(cx, |map, head, goal| movement::up(map, head, goal, false))
}
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
@@ -3606,13 +3608,13 @@ impl Editor {
if !selection.is_empty() {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) = movement::down(&map, selection.end, selection.goal);
let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false);
selection.collapse_to(cursor, goal);
});
}
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
self.move_selection_heads(cx, movement::down)
self.move_selection_heads(cx, |map, head, goal| movement::down(map, head, goal, false))
}
pub fn move_to_previous_word_start(

View File

@@ -16,6 +16,7 @@ use gpui::{
PathBuilder,
},
json::{self, ToJson},
platform::CursorStyle,
text_layout::{self, Line, RunStyle, TextLayoutCache},
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
@@ -329,6 +330,7 @@ impl EditorElement {
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
cx.scene.push_layer(Some(bounds));
cx.scene.push_cursor_style(bounds, CursorStyle::IBeam);
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(

View File

@@ -28,6 +28,7 @@ pub fn up(
map: &DisplaySnapshot,
start: DisplayPoint,
goal: SelectionGoal,
preserve_column_at_start: bool,
) -> (DisplayPoint, SelectionGoal) {
let mut goal_column = if let SelectionGoal::Column(column) = goal {
column
@@ -42,6 +43,8 @@ pub fn up(
);
if point.row() < start.row() {
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
} else if preserve_column_at_start {
return (start, goal);
} else {
point = DisplayPoint::new(0, 0);
goal_column = 0;
@@ -63,6 +66,7 @@ pub fn down(
map: &DisplaySnapshot,
start: DisplayPoint,
goal: SelectionGoal,
preserve_column_at_end: bool,
) -> (DisplayPoint, SelectionGoal) {
let mut goal_column = if let SelectionGoal::Column(column) = goal {
column
@@ -74,6 +78,8 @@ pub fn down(
let mut point = map.clip_point(DisplayPoint::new(next_row, 0), Bias::Right);
if point.row() > start.row() {
*point.column_mut() = map.column_from_chars(point.row(), goal_column);
} else if preserve_column_at_end {
return (start, goal);
} else {
point = map.max_point();
goal_column = map.column_to_chars(point.row(), point.column())
@@ -503,41 +509,81 @@ mod tests {
// Can't move up into the first excerpt's header
assert_eq!(
up(&snapshot, DisplayPoint::new(2, 2), SelectionGoal::Column(2)),
up(
&snapshot,
DisplayPoint::new(2, 2),
SelectionGoal::Column(2),
false
),
(DisplayPoint::new(2, 0), SelectionGoal::Column(0)),
);
assert_eq!(
up(&snapshot, DisplayPoint::new(2, 0), SelectionGoal::None),
up(
&snapshot,
DisplayPoint::new(2, 0),
SelectionGoal::None,
false
),
(DisplayPoint::new(2, 0), SelectionGoal::Column(0)),
);
// Move up and down within first excerpt
assert_eq!(
up(&snapshot, DisplayPoint::new(3, 4), SelectionGoal::Column(4)),
up(
&snapshot,
DisplayPoint::new(3, 4),
SelectionGoal::Column(4),
false
),
(DisplayPoint::new(2, 3), SelectionGoal::Column(4)),
);
assert_eq!(
down(&snapshot, DisplayPoint::new(2, 3), SelectionGoal::Column(4)),
down(
&snapshot,
DisplayPoint::new(2, 3),
SelectionGoal::Column(4),
false
),
(DisplayPoint::new(3, 4), SelectionGoal::Column(4)),
);
// Move up and down across second excerpt's header
assert_eq!(
up(&snapshot, DisplayPoint::new(6, 5), SelectionGoal::Column(5)),
up(
&snapshot,
DisplayPoint::new(6, 5),
SelectionGoal::Column(5),
false
),
(DisplayPoint::new(3, 4), SelectionGoal::Column(5)),
);
assert_eq!(
down(&snapshot, DisplayPoint::new(3, 4), SelectionGoal::Column(5)),
down(
&snapshot,
DisplayPoint::new(3, 4),
SelectionGoal::Column(5),
false
),
(DisplayPoint::new(6, 5), SelectionGoal::Column(5)),
);
// Can't move down off the end
assert_eq!(
down(&snapshot, DisplayPoint::new(7, 0), SelectionGoal::Column(0)),
down(
&snapshot,
DisplayPoint::new(7, 0),
SelectionGoal::Column(0),
false
),
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
);
assert_eq!(
down(&snapshot, DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
down(
&snapshot,
DisplayPoint::new(7, 2),
SelectionGoal::Column(2),
false
),
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
);
}

View File

@@ -161,29 +161,25 @@ impl View for GoToLine {
self.max_point.row + 1
);
Align::new(
ConstrainedBox::new(
Container::new(
Flex::new(Axis::Vertical)
.with_child(
Container::new(ChildView::new(&self.line_editor).boxed())
.with_style(theme.input_editor.container)
.boxed(),
)
.with_child(
Container::new(Label::new(label, theme.empty.label.clone()).boxed())
.with_style(theme.empty.container)
.boxed(),
)
.boxed(),
)
.with_style(theme.container)
.boxed(),
ConstrainedBox::new(
Container::new(
Flex::new(Axis::Vertical)
.with_child(
Container::new(ChildView::new(&self.line_editor).boxed())
.with_style(theme.input_editor.container)
.boxed(),
)
.with_child(
Container::new(Label::new(label, theme.empty.label.clone()).boxed())
.with_style(theme.empty.container)
.boxed(),
)
.boxed(),
)
.with_max_width(500.0)
.with_style(theme.container)
.boxed(),
)
.top()
.with_max_width(500.0)
.named("go to line")
}

View File

@@ -4,7 +4,7 @@ use crate::{
elements::ElementBox,
executor::{self, Task},
keymap::{self, Binding, Keystroke},
platform::{self, CursorStyle, Platform, PromptLevel, WindowOptions},
platform::{self, Platform, PromptLevel, WindowOptions},
presenter::Presenter,
util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
@@ -31,10 +31,7 @@ use std::{
path::{Path, PathBuf},
pin::Pin,
rc::{self, Rc},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak,
},
sync::{Arc, Weak},
time::Duration,
};
@@ -766,7 +763,6 @@ pub struct MutableAppContext {
pending_global_notifications: HashSet<TypeId>,
pending_flushes: usize,
flushing_effects: bool,
next_cursor_style_handle_id: Arc<AtomicUsize>,
halt_action_dispatch: bool,
}
@@ -818,7 +814,6 @@ impl MutableAppContext {
pending_global_notifications: HashSet::new(),
pending_flushes: 0,
flushing_effects: false,
next_cursor_style_handle_id: Default::default(),
halt_action_dispatch: false,
}
}
@@ -1596,6 +1591,7 @@ impl MutableAppContext {
Window {
root_view: root_view.clone().into(),
focused_view_id: Some(root_view.id()),
is_active: true,
invalidation: None,
},
);
@@ -1643,10 +1639,17 @@ impl MutableAppContext {
}));
}
{
let mut app = self.upgrade();
window.on_active_status_change(Box::new(move |is_active| {
app.update(|cx| cx.window_changed_active_status(window_id, is_active))
}));
}
{
let mut app = self.upgrade();
window.on_resize(Box::new(move || {
app.update(|cx| cx.resize_window(window_id))
app.update(|cx| cx.window_was_resized(window_id))
}));
}
@@ -1861,6 +1864,10 @@ impl MutableAppContext {
.get_or_insert(WindowInvalidation::default());
}
}
Effect::ActivateWindow {
window_id,
is_active,
} => self.handle_activation_effect(window_id, is_active),
Effect::RefreshWindows => {
refreshing = true;
}
@@ -1919,11 +1926,18 @@ impl MutableAppContext {
}
}
fn resize_window(&mut self, window_id: usize) {
fn window_was_resized(&mut self, window_id: usize) {
self.pending_effects
.push_back(Effect::ResizeWindow { window_id });
}
fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) {
self.pending_effects.push_back(Effect::ActivateWindow {
window_id,
is_active,
});
}
pub fn refresh_windows(&mut self) {
self.pending_effects.push_back(Effect::RefreshWindows);
}
@@ -1949,16 +1963,6 @@ impl MutableAppContext {
self.presenters_and_platform_windows = presenters;
}
pub fn set_cursor_style(&mut self, style: CursorStyle) -> CursorStyleHandle {
self.platform.set_cursor_style(style);
let id = self.next_cursor_style_handle_id.fetch_add(1, SeqCst);
CursorStyleHandle {
id,
next_cursor_style_handle_id: self.next_cursor_style_handle_id.clone(),
platform: self.platform(),
}
}
fn handle_subscription_effect(
&mut self,
entity_id: usize,
@@ -2219,6 +2223,34 @@ impl MutableAppContext {
}
}
fn handle_activation_effect(&mut self, window_id: usize, active: bool) {
if self
.cx
.windows
.get(&window_id)
.map(|w| w.is_active)
.map_or(false, |cur_active| cur_active == active)
{
return;
}
self.update(|this| {
let window = this.cx.windows.get_mut(&window_id)?;
window.is_active = active;
let view_id = window.focused_view_id?;
if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
if active {
view.on_focus(this, window_id, view_id);
} else {
view.on_blur(this, window_id, view_id);
}
this.cx.views.insert((window_id, view_id), view);
}
Some(())
});
}
fn handle_focus_effect(&mut self, window_id: usize, focused_id: Option<usize>) {
self.pending_focus_index.take();
@@ -2582,6 +2614,7 @@ impl ReadView for AppContext {
struct Window {
root_view: AnyViewHandle,
focused_view_id: Option<usize>,
is_active: bool,
invalidation: Option<WindowInvalidation>,
}
@@ -2648,6 +2681,10 @@ pub enum Effect {
ResizeWindow {
window_id: usize,
},
ActivateWindow {
window_id: usize,
is_active: bool,
},
RefreshWindows,
}
@@ -2729,6 +2766,14 @@ impl Debug for Effect {
.debug_struct("Effect::RefreshWindow")
.field("window_id", window_id)
.finish(),
Effect::ActivateWindow {
window_id,
is_active,
} => f
.debug_struct("Effect::ActivateWindow")
.field("window_id", window_id)
.field("is_active", is_active)
.finish(),
Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(),
}
}
@@ -4452,20 +4497,6 @@ impl<T> Drop for ElementStateHandle<T> {
}
}
pub struct CursorStyleHandle {
id: usize,
next_cursor_style_handle_id: Arc<AtomicUsize>,
platform: Arc<dyn Platform>,
}
impl Drop for CursorStyleHandle {
fn drop(&mut self) {
if self.id + 1 == self.next_cursor_style_handle_id.load(SeqCst) {
self.platform.set_cursor_style(CursorStyle::Arrow);
}
}
}
#[must_use]
pub enum Subscription {
Subscription {

View File

@@ -1,17 +1,17 @@
use pathfinder_geometry::rect::RectF;
use serde::Deserialize;
use serde_json::json;
use crate::{
color::Color,
geometry::{
deserialize_vec2f,
rect::RectF,
vector::{vec2f, Vector2F},
},
json::ToJson,
platform::CursorStyle,
scene::{self, Border, Quad},
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use serde::Deserialize;
use serde_json::json;
#[derive(Clone, Copy, Debug, Default, Deserialize)]
pub struct ContainerStyle {
@@ -27,6 +27,8 @@ pub struct ContainerStyle {
pub corner_radius: f32,
#[serde(default)]
pub shadow: Option<Shadow>,
#[serde(default)]
pub cursor: Option<CursorStyle>,
}
pub struct Container {
@@ -128,6 +130,11 @@ impl Container {
self
}
pub fn with_cursor(mut self, style: CursorStyle) -> Self {
self.style.cursor = Some(style);
self
}
fn margin_size(&self) -> Vector2F {
vec2f(
self.style.margin.left + self.style.margin.right,
@@ -205,6 +212,10 @@ impl Element for Container {
});
}
if let Some(style) = self.style.cursor {
cx.scene.push_cursor_style(quad_bounds, style);
}
let child_origin =
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);

View File

@@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
platform::CursorStyle,
CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle,
Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
EventContext, LayoutContext, PaintContext, SizeConstraint,
};
use serde_json::json;
@@ -25,7 +25,6 @@ pub struct MouseState {
pub hovered: bool,
pub clicked: bool,
prev_drag_position: Option<Vector2F>,
cursor_style_handle: Option<CursorStyleHandle>,
}
impl MouseEventHandler {
@@ -72,6 +71,14 @@ impl MouseEventHandler {
self.padding = padding;
self
}
fn hit_bounds(&self, bounds: RectF) -> RectF {
RectF::from_points(
bounds.origin() - vec2f(self.padding.left, self.padding.top),
bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
)
.round_out()
}
}
impl Element for MouseEventHandler {
@@ -93,6 +100,10 @@ impl Element for MouseEventHandler {
_: &mut Self::LayoutState,
cx: &mut PaintContext,
) -> Self::PaintState {
if let Some(cursor_style) = self.cursor_style {
cx.scene
.push_cursor_style(self.hit_bounds(bounds), cursor_style);
}
self.child.paint(bounds.origin(), visible_bounds, cx);
}
@@ -105,19 +116,13 @@ impl Element for MouseEventHandler {
_: &mut Self::PaintState,
cx: &mut EventContext,
) -> bool {
let cursor_style = self.cursor_style;
let hit_bounds = self.hit_bounds(visible_bounds);
let mouse_down_handler = self.mouse_down_handler.as_mut();
let click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_handler.as_mut();
let handled_in_child = self.child.dispatch_event(event, cx);
let hit_bounds = RectF::from_points(
visible_bounds.origin() - vec2f(self.padding.left, self.padding.top),
visible_bounds.lower_right() + vec2f(self.padding.right, self.padding.bottom),
)
.round_out();
self.state.update(cx, |state, cx| match event {
Event::MouseMoved {
position,
@@ -127,16 +132,6 @@ impl Element for MouseEventHandler {
let mouse_in = hit_bounds.contains_point(*position);
if state.hovered != mouse_in {
state.hovered = mouse_in;
if let Some(cursor_style) = cursor_style {
if !state.clicked {
if state.hovered {
state.cursor_style_handle =
Some(cx.set_cursor_style(cursor_style));
} else {
state.cursor_style_handle = None;
}
}
}
cx.notify();
return true;
}
@@ -160,9 +155,6 @@ impl Element for MouseEventHandler {
state.prev_drag_position = None;
if !handled_in_child && state.clicked {
state.clicked = false;
if !state.hovered {
state.cursor_style_handle = None;
}
cx.notify();
if let Some(handler) = click_handler {
if hit_bounds.contains_point(*position) {

View File

@@ -21,6 +21,7 @@ use anyhow::{anyhow, Result};
use async_task::Runnable;
pub use event::{Event, NavigationDirection};
use postage::oneshot;
use serde::Deserialize;
use std::{
any::Any,
path::{Path, PathBuf},
@@ -86,6 +87,7 @@ pub trait Dispatcher: Send + Sync {
pub trait Window: WindowContext {
fn as_any_mut(&mut self) -> &mut dyn Any;
fn on_event(&mut self, callback: Box<dyn FnMut(Event)>);
fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
@@ -125,11 +127,12 @@ pub enum PromptLevel {
Critical,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, Deserialize)]
pub enum CursorStyle {
Arrow,
ResizeLeftRight,
PointingHand,
IBeam,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]

View File

@@ -583,6 +583,7 @@ impl platform::Platform for MacPlatform {
CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
};
let _: () = msg_send![cursor, set];
}

View File

@@ -73,6 +73,14 @@ unsafe fn build_classes() {
sel!(windowDidResize:),
window_did_resize as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidBecomeKey:),
window_did_change_key_status as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(windowDidResignKey:),
window_did_change_key_status as extern "C" fn(&Object, Sel, id),
);
decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
decl.register()
};
@@ -157,6 +165,7 @@ struct WindowState {
id: usize,
native_window: id,
event_callback: Option<Box<dyn FnMut(Event)>>,
activate_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut()>>,
close_callback: Option<Box<dyn FnOnce()>>,
synthetic_drag_counter: usize,
@@ -234,6 +243,7 @@ impl Window {
event_callback: None,
resize_callback: None,
close_callback: None,
activate_callback: None,
synthetic_drag_counter: 0,
executor,
scene_to_render: Default::default(),
@@ -333,6 +343,10 @@ impl platform::Window for Window {
self.0.as_ref().borrow_mut().close_callback = Some(callback);
}
fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
self.0.as_ref().borrow_mut().activate_callback = Some(callback);
}
fn prompt(
&self,
level: platform::PromptLevel,
@@ -598,6 +612,29 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
window_state.as_ref().borrow().move_traffic_light();
}
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
let is_active = if selector == sel!(windowDidBecomeKey:) {
true
} else if selector == sel!(windowDidResignKey:) {
false
} else {
unreachable!();
};
let window_state = unsafe { get_window_state(this) };
let executor = window_state.as_ref().borrow().executor.clone();
executor
.spawn(async move {
let mut window_state_borrow = window_state.as_ref().borrow_mut();
if let Some(mut callback) = window_state_borrow.activate_callback.take() {
drop(window_state_borrow);
callback(is_active);
window_state.borrow_mut().activate_callback = Some(callback);
};
})
.detach();
}
extern "C" fn close_window(this: &Object, _: Sel) {
unsafe {
let close_callback = {

View File

@@ -229,6 +229,8 @@ impl super::Window for Window {
self.event_handlers.push(callback);
}
fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
self.resize_handlers.push(callback);
}

View File

@@ -4,7 +4,7 @@ use crate::{
font_cache::FontCache,
geometry::rect::RectF,
json::{self, ToJson},
platform::Event,
platform::{CursorStyle, Event},
text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox,
ElementStateContext, Entity, FontSystem, ModelHandle, ReadModel, ReadView, Scene,
@@ -22,6 +22,7 @@ pub struct Presenter {
window_id: usize,
pub(crate) rendered_views: HashMap<usize, ElementBox>,
parents: HashMap<usize, usize>,
cursor_styles: Vec<(RectF, CursorStyle)>,
font_cache: Arc<FontCache>,
text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>,
@@ -42,6 +43,7 @@ impl Presenter {
window_id,
rendered_views: cx.render_views(window_id, titlebar_height),
parents: HashMap::new(),
cursor_styles: Default::default(),
font_cache,
text_layout_cache,
asset_cache,
@@ -118,6 +120,7 @@ impl Presenter {
RectF::new(Vector2F::zero(), window_size),
);
self.text_layout_cache.finish_frame();
self.cursor_styles = scene.cursor_styles();
if let Some(event) = self.last_mouse_moved_event.clone() {
self.dispatch_event(event, cx)
@@ -171,8 +174,22 @@ impl Presenter {
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
match event {
Event::MouseMoved { .. } => {
Event::MouseMoved {
position,
left_mouse_down,
} => {
self.last_mouse_moved_event = Some(event.clone());
if !left_mouse_down {
let mut style_to_assign = CursorStyle::Arrow;
for (bounds, style) in self.cursor_styles.iter().rev() {
if bounds.contains_point(position) {
style_to_assign = *style;
break;
}
}
cx.platform().set_cursor_style(style_to_assign);
}
}
Event::LeftMouseDragged { position } => {
self.last_mouse_moved_event = Some(Event::MouseMoved {

View File

@@ -7,6 +7,7 @@ use crate::{
fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::CursorStyle,
ImageData,
};
@@ -32,6 +33,7 @@ pub struct Layer {
image_glyphs: Vec<ImageGlyph>,
icons: Vec<Icon>,
paths: Vec<Path>,
cursor_styles: Vec<(RectF, CursorStyle)>,
}
#[derive(Default, Debug)]
@@ -173,6 +175,13 @@ impl Scene {
self.stacking_contexts.iter().flat_map(|s| &s.layers)
}
pub fn cursor_styles(&self) -> Vec<(RectF, CursorStyle)> {
self.layers()
.flat_map(|layer| &layer.cursor_styles)
.copied()
.collect()
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context_stack
.push(self.stacking_contexts.len());
@@ -197,6 +206,10 @@ impl Scene {
self.active_layer().push_quad(quad)
}
pub fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) {
self.active_layer().push_cursor_style(bounds, style);
}
pub fn push_image(&mut self, image: Image) {
self.active_layer().push_image(image)
}
@@ -285,6 +298,7 @@ impl Layer {
glyphs: Default::default(),
icons: Default::default(),
paths: Default::default(),
cursor_styles: Default::default(),
}
}
@@ -302,6 +316,14 @@ impl Layer {
self.quads.as_slice()
}
fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) {
if let Some(bounds) = bounds.intersection(self.clip_bounds.unwrap_or(bounds)) {
if can_draw(bounds) {
self.cursor_styles.push((bounds, style));
}
}
}
fn push_underline(&mut self, underline: Underline) {
if underline.width > 0. {
self.underlines.push(underline);

View File

@@ -15,6 +15,7 @@ test-support = [
"lsp/test-support",
"text/test-support",
"tree-sitter-rust",
"tree-sitter-typescript",
"util/test-support",
]
@@ -45,7 +46,8 @@ similar = "1.3"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
tree-sitter = "0.20"
tree-sitter-rust = { version = "0.20.0", optional = true }
tree-sitter-rust = { version = "*", optional = true }
tree-sitter-typescript = { version = "*", optional = true }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
@@ -57,6 +59,6 @@ util = { path = "../util", features = ["test-support"] }
ctor = "0.1"
env_logger = "0.8"
rand = "0.8.3"
tree-sitter-json = "0.19.0"
tree-sitter-rust = "0.20.0"
tree-sitter-json = "*"
tree-sitter-rust = "*"
unindent = "0.1.7"

View File

@@ -38,7 +38,7 @@ use tree_sitter::{InputEdit, QueryCursor, Tree};
use util::TryFutureExt as _;
#[cfg(any(test, feature = "test-support"))]
pub use tree_sitter_rust;
pub use {tree_sitter_rust, tree_sitter_typescript};
pub use lsp::DiagnosticSeverity;
@@ -1638,6 +1638,34 @@ impl BufferSnapshot {
.and_then(|language| language.grammar.as_ref())
}
pub fn range_for_word_token_at<T: ToOffset + ToPoint>(
&self,
position: T,
) -> Option<Range<usize>> {
let offset = position.to_offset(self);
// Find the first leaf node that touches the position.
let tree = self.tree.as_ref()?;
let mut cursor = tree.root_node().walk();
while cursor.goto_first_child_for_byte(offset).is_some() {}
let node = cursor.node();
if node.child_count() > 0 {
return None;
}
// Check that the leaf node contains word characters.
let range = node.byte_range();
if self
.text_for_range(range.clone())
.flat_map(str::chars)
.any(|c| c.is_alphanumeric())
{
return Some(range);
} else {
None
}
}
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let tree = self.tree.as_ref()?;
let range = range.start.to_offset(self)..range.end.to_offset(self);
@@ -1730,7 +1758,7 @@ impl BufferSnapshot {
.and_then(|language| language.grammar.as_ref())?;
let mut cursor = QueryCursorHandle::new();
cursor.set_byte_range(range);
cursor.set_byte_range(range.clone());
let matches = cursor.matches(
&grammar.outline_query,
tree.root_node(),
@@ -1750,7 +1778,10 @@ impl BufferSnapshot {
let items = matches
.filter_map(|mat| {
let item_node = mat.nodes_for_capture_index(item_capture_ix).next()?;
let range = item_node.start_byte()..item_node.end_byte();
let item_range = item_node.start_byte()..item_node.end_byte();
if item_range.end < range.start || item_range.start > range.end {
return None;
}
let mut text = String::new();
let mut name_ranges = Vec::new();
let mut highlight_ranges = Vec::new();
@@ -1808,15 +1839,15 @@ impl BufferSnapshot {
}
while stack.last().map_or(false, |prev_range| {
!prev_range.contains(&range.start) || !prev_range.contains(&range.end)
!prev_range.contains(&item_range.start) || !prev_range.contains(&item_range.end)
}) {
stack.pop();
}
stack.push(range.clone());
stack.push(item_range.clone());
Some(OutlineItem {
depth: stack.len() - 1,
range: self.anchor_after(range.start)..self.anchor_before(range.end),
range: self.anchor_after(item_range.start)..self.anchor_before(item_range.end),
text,
highlight_ranges,
name_ranges,

View File

@@ -51,6 +51,10 @@ impl HighlightMap {
}
impl HighlightId {
pub fn is_default(&self) -> bool {
*self == DEFAULT_SYNTAX_HIGHLIGHT_ID
}
pub fn style(&self, theme: &SyntaxTheme) -> Option<HighlightStyle> {
theme
.highlights

View File

@@ -546,7 +546,9 @@ impl Language {
{
let end_offset = offset + chunk.text.len();
if let Some(highlight_id) = chunk.syntax_highlight_id {
result.push((offset..end_offset, highlight_id));
if !highlight_id.is_default() {
result.push((offset..end_offset, highlight_id));
}
}
offset = end_offset;
}

View File

@@ -99,8 +99,6 @@ impl<D: PickerDelegate> View for Picker<D> {
.constrained()
.with_max_width(self.max_size.x())
.with_max_height(self.max_size.y())
.aligned()
.top()
.named("picker")
}

View File

@@ -669,6 +669,10 @@ impl Project {
.map(|worktree| worktree.read(cx).id())
}
pub fn can_share(&self, cx: &AppContext) -> bool {
self.is_local() && self.visible_worktrees(cx).next().is_some()
}
pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let rpc = self.client.clone();
cx.spawn(|this, mut cx| async move {
@@ -2488,26 +2492,51 @@ impl Project {
};
source_buffer_handle.read_with(&cx, |this, _| {
let snapshot = this.snapshot();
let clipped_position = this.clip_point_utf16(position, Bias::Left);
let mut range_for_token = None;
Ok(completions
.into_iter()
.filter_map(|lsp_completion| {
let (old_range, new_text) = match lsp_completion.text_edit.as_ref() {
// If the language server provides a range to overwrite, then
// check that the range is valid.
Some(lsp::CompletionTextEdit::Edit(edit)) => {
(range_from_lsp(edit.range), edit.new_text.clone())
}
None => {
let clipped_position =
this.clip_point_utf16(position, Bias::Left);
if position != clipped_position {
let range = range_from_lsp(edit.range);
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
if start != range.start || end != range.end {
log::info!("completion out of expected range");
return None;
}
(
this.common_prefix_at(
clipped_position,
&lsp_completion.label,
),
lsp_completion.label.clone(),
snapshot.anchor_before(start)..snapshot.anchor_after(end),
edit.new_text.clone(),
)
}
// If the language server does not provide a range, then infer
// the range based on the syntax tree.
None => {
if position != clipped_position {
log::info!("completion out of expected range");
return None;
}
let Range { start, end } = range_for_token
.get_or_insert_with(|| {
let offset = position.to_offset(&snapshot);
snapshot
.range_for_word_token_at(offset)
.unwrap_or_else(|| offset..offset)
})
.clone();
let text = lsp_completion
.insert_text
.as_ref()
.unwrap_or(&lsp_completion.label)
.clone();
(
snapshot.anchor_before(start)..snapshot.anchor_after(end),
text.clone(),
)
}
Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => {
@@ -2516,28 +2545,20 @@ impl Project {
}
};
let clipped_start = this.clip_point_utf16(old_range.start, Bias::Left);
let clipped_end = this.clip_point_utf16(old_range.end, Bias::Left);
if clipped_start == old_range.start && clipped_end == old_range.end {
Some(Completion {
old_range: this.anchor_before(old_range.start)
..this.anchor_after(old_range.end),
new_text,
label: language
.as_ref()
.and_then(|l| l.label_for_completion(&lsp_completion))
.unwrap_or_else(|| {
CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
}),
lsp_completion,
})
} else {
log::info!("completion out of expected range");
None
}
Some(Completion {
old_range,
new_text,
label: language
.as_ref()
.and_then(|l| l.label_for_completion(&lsp_completion))
.unwrap_or_else(|| {
CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
}),
lsp_completion,
})
})
.collect())
})
@@ -4889,8 +4910,8 @@ mod tests {
use futures::{future, StreamExt};
use gpui::test::subscribe;
use language::{
tree_sitter_rust, Diagnostic, FakeLspAdapter, LanguageConfig, OffsetRangeExt, Point,
ToPoint,
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
OffsetRangeExt, Point, ToPoint,
};
use lsp::Url;
use serde_json::json;
@@ -6507,6 +6528,75 @@ mod tests {
}
}
#[gpui::test]
async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
let mut language = Language::new(
LanguageConfig {
name: "TypeScript".into(),
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
Some(tree_sitter_typescript::language_typescript()),
);
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/dir",
json!({
"a.ts": "",
}),
)
.await;
let project = Project::test(fs, cx);
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
let (tree, _) = project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/dir", true, cx)
})
.await
.unwrap();
let worktree_id = tree.read_with(cx, |tree, _| tree.id());
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
let buffer = project
.update(cx, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx))
.await
.unwrap();
let fake_server = fake_language_servers.next().await.unwrap();
let text = "let a = b.fqn";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, cx| {
project.completions(&buffer, text.len(), cx)
});
fake_server
.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "fullyQualifiedName?".into(),
insert_text: Some("fullyQualifiedName".into()),
..Default::default()
},
])))
})
.next()
.await;
let completions = completions.await.unwrap();
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
assert_eq!(completions.len(), 1);
assert_eq!(completions[0].new_text, "fullyQualifiedName");
assert_eq!(
completions[0].old_range.to_offset(&snapshot),
text.len() - 3..text.len()
);
}
#[gpui::test(iterations = 10)]
async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
let mut language = Language::new(

View File

@@ -43,6 +43,7 @@ pub struct Workspace {
pub status_bar: StatusBar,
pub toolbar: Toolbar,
pub disconnected_overlay: ContainedText,
pub modal: ContainerStyle,
}
#[derive(Clone, Deserialize, Default)]
@@ -54,8 +55,10 @@ pub struct Titlebar {
pub avatar_width: f32,
pub avatar_ribbon: AvatarRibbon,
pub offline_icon: OfflineIcon,
pub share_icon_color: Color,
pub share_icon_active_color: Color,
pub share_icon: ShareIcon,
pub hovered_share_icon: ShareIcon,
pub active_share_icon: ShareIcon,
pub hovered_active_share_icon: ShareIcon,
pub avatar: ImageStyle,
pub sign_in_prompt: ContainedText,
pub hovered_sign_in_prompt: ContainedText,
@@ -78,6 +81,13 @@ pub struct OfflineIcon {
pub color: Color,
}
#[derive(Clone, Deserialize, Default)]
pub struct ShareIcon {
#[serde(flatten)]
pub container: ContainerStyle,
pub color: Color,
}
#[derive(Clone, Deserialize, Default)]
pub struct Tab {
pub height: f32,

View File

@@ -36,7 +36,12 @@ impl ThemeSelector {
let handle = cx.weak_handle();
let picker = cx.add_view(|cx| Picker::new(handle, cx));
let original_theme = cx.global::<Settings>().theme.clone();
let theme_names = registry.list().collect::<Vec<_>>();
let mut theme_names = registry.list().collect::<Vec<_>>();
theme_names.sort_unstable_by(|a, b| {
a.ends_with("dark")
.cmp(&b.ends_with("dark"))
.then_with(|| a.cmp(&b))
});
let matches = theme_names
.iter()
.map(|name| StringMatch {

View File

@@ -28,7 +28,7 @@ mod test {
#[gpui::test]
async fn test_enter_and_exit_insert_mode(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true, "").await;
let mut cx = VimTestContext::new(cx, true).await;
cx.simulate_keystroke("i");
assert_eq!(cx.mode(), Mode::Insert);
cx.simulate_keystrokes(["T", "e", "s", "t"]);

View File

@@ -4,7 +4,7 @@ use editor::{
movement, Bias, DisplayPoint,
};
use gpui::{actions, impl_actions, MutableAppContext};
use language::SelectionGoal;
use language::{Selection, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@@ -14,22 +14,15 @@ use crate::{
Vim,
};
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum Motion {
Left,
Down,
Up,
Right,
NextWordStart {
ignore_punctuation: bool,
stop_at_newline: bool,
},
NextWordEnd {
ignore_punctuation: bool,
},
PreviousWordStart {
ignore_punctuation: bool,
},
NextWordStart { ignore_punctuation: bool },
NextWordEnd { ignore_punctuation: bool },
PreviousWordStart { ignore_punctuation: bool },
StartOfLine,
EndOfLine,
StartOfDocument,
@@ -41,8 +34,6 @@ pub enum Motion {
struct NextWordStart {
#[serde(default)]
ignore_punctuation: bool,
#[serde(default)]
stop_at_newline: bool,
}
#[derive(Clone, Deserialize)]
@@ -87,19 +78,8 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(|_: &mut Workspace, _: &EndOfDocument, cx: _| motion(Motion::EndOfDocument, cx));
cx.add_action(
|_: &mut Workspace,
&NextWordStart {
ignore_punctuation,
stop_at_newline,
}: &NextWordStart,
cx: _| {
motion(
Motion::NextWordStart {
ignore_punctuation,
stop_at_newline,
},
cx,
)
|_: &mut Workspace, &NextWordStart { ignore_punctuation }: &NextWordStart, cx: _| {
motion(Motion::NextWordStart { ignore_punctuation }, cx)
},
);
cx.add_action(
@@ -128,29 +108,48 @@ fn motion(motion: Motion, cx: &mut MutableAppContext) {
}
}
// Motion handling is specified here:
// https://github.com/vim/vim/blob/master/runtime/doc/motion.txt
impl Motion {
pub fn linewise(self) -> bool {
use Motion::*;
match self {
Down | Up | StartOfDocument | EndOfDocument => true,
_ => false,
}
}
pub fn inclusive(self) -> bool {
use Motion::*;
if self.linewise() {
return true;
}
match self {
EndOfLine | NextWordEnd { .. } => true,
Left | Right | StartOfLine | NextWordStart { .. } | PreviousWordStart { .. } => false,
_ => panic!("Exclusivity not defined for {self:?}"),
}
}
pub fn move_point(
self,
map: &DisplaySnapshot,
point: DisplayPoint,
goal: SelectionGoal,
block_cursor_positioning: bool,
) -> (DisplayPoint, SelectionGoal) {
use Motion::*;
match self {
Left => (left(map, point), SelectionGoal::None),
Down => movement::down(map, point, goal),
Up => movement::up(map, point, goal),
Down => movement::down(map, point, goal, true),
Up => movement::up(map, point, goal, true),
Right => (right(map, point), SelectionGoal::None),
NextWordStart {
ignore_punctuation,
stop_at_newline,
} => (
next_word_start(map, point, ignore_punctuation, stop_at_newline),
NextWordStart { ignore_punctuation } => (
next_word_start(map, point, ignore_punctuation),
SelectionGoal::None,
),
NextWordEnd { ignore_punctuation } => (
next_word_end(map, point, ignore_punctuation, block_cursor_positioning),
next_word_end(map, point, ignore_punctuation),
SelectionGoal::None,
),
PreviousWordStart { ignore_punctuation } => (
@@ -164,11 +163,55 @@ impl Motion {
}
}
pub fn line_wise(self) -> bool {
use Motion::*;
match self {
Down | Up | StartOfDocument | EndOfDocument => true,
_ => false,
// Expands a selection using self motion for an operator
pub fn expand_selection(
self,
map: &DisplaySnapshot,
selection: &mut Selection<DisplayPoint>,
expand_to_surrounding_newline: bool,
) {
let (head, goal) = self.move_point(map, selection.head(), selection.goal);
selection.set_head(head, goal);
if self.linewise() {
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
if expand_to_surrounding_newline {
if selection.end.row() < map.max_point().row() {
*selection.end.row_mut() += 1;
*selection.end.column_mut() = 0;
// Don't reset the end here
return;
} else if selection.start.row() > 0 {
*selection.start.row_mut() -= 1;
*selection.start.column_mut() = map.line_len(selection.start.row());
}
}
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
} 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
&& 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;
}
}
}
}
@@ -187,7 +230,6 @@ fn next_word_start(
map: &DisplaySnapshot,
point: DisplayPoint,
ignore_punctuation: bool,
stop_at_newline: bool,
) -> DisplayPoint {
let mut crossed_newline = false;
movement::find_boundary(map, point, |left, right| {
@@ -196,8 +238,8 @@ fn next_word_start(
let at_newline = right == '\n';
let found = (left_kind != right_kind && !right.is_whitespace())
|| (at_newline && (crossed_newline || stop_at_newline))
|| (at_newline && left == '\n'); // Prevents skipping repeated empty lines
|| at_newline && crossed_newline
|| at_newline && left == '\n'; // Prevents skipping repeated empty lines
if at_newline {
crossed_newline = true;
@@ -210,7 +252,6 @@ fn next_word_end(
map: &DisplaySnapshot,
mut point: DisplayPoint,
ignore_punctuation: bool,
before_end_character: bool,
) -> DisplayPoint {
*point.column_mut() += 1;
point = movement::find_boundary(map, point, |left, right| {
@@ -221,13 +262,12 @@ 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 backtraced already
if before_end_character
&& !map
.chars_at(point)
.skip(1)
.next()
.map(|c| c == '\n')
.unwrap_or(true)
if !map
.chars_at(point)
.skip(1)
.next()
.map(|c| c == '\n')
.unwrap_or(true)
{
*point.column_mut() = point.column().saturating_sub(1);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
use crate::{motion::Motion, state::Mode, Vim};
use editor::{char_kind, movement};
use gpui::{impl_actions, MutableAppContext, ViewContext};
use serde::Deserialize;
use workspace::Workspace;
#[derive(Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ChangeWord {
#[serde(default)]
ignore_punctuation: bool,
}
impl_actions!(vim, [ChangeWord]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(change_word);
}
pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
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.move_selections(cx, |map, selection| {
motion.expand_selection(map, selection, false);
});
editor.insert(&"", cx);
});
});
vim.switch_mode(Mode::Insert, cx)
}
// 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 change_word(
_: &mut Workspace,
&ChangeWord { ignore_punctuation }: &ChangeWord,
cx: &mut ViewContext<Workspace>,
) {
Vim::update(cx, |vim, cx| {
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.move_selections(cx, |map, selection| {
if 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'
});
});
editor.insert(&"", cx);
});
});
vim.switch_mode(Mode::Insert, cx);
});
}
#[cfg(test)]
mod test {
use indoc::indoc;
use crate::{state::Mode, vim_test_context::VimTestContext};
#[gpui::test]
async fn test_change_h(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "h"]).mode_after(Mode::Insert);
cx.assert("Te|st", "T|st");
cx.assert("T|est", "|est");
cx.assert("|Test", "|Test");
cx.assert(
indoc! {"
Test
|test"},
indoc! {"
Test
|test"},
);
}
#[gpui::test]
async fn test_change_l(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "l"]).mode_after(Mode::Insert);
cx.assert("Te|st", "Te|t");
cx.assert("Tes|t", "Tes|");
}
#[gpui::test]
async fn test_change_w(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "w"]).mode_after(Mode::Insert);
cx.assert("Te|st", "Te|");
cx.assert("T|est test", "T| test");
cx.assert("Test| test", "Test|test");
cx.assert(
indoc! {"
Test te|st
test"},
indoc! {"
Test te|
test"},
);
cx.assert(
indoc! {"
Test tes|t
test"},
indoc! {"
Test tes|
test"},
);
cx.assert(
indoc! {"
Test test
|
test"},
indoc! {"
Test test
|
test"},
);
let mut cx = cx.binding(["c", "shift-W"]);
cx.assert("Test te|st-test test", "Test te| test");
}
#[gpui::test]
async fn test_change_e(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "e"]).mode_after(Mode::Insert);
cx.assert("Te|st Test", "Te| Test");
cx.assert("T|est test", "T| test");
cx.assert(
indoc! {"
Test te|st
test"},
indoc! {"
Test te|
test"},
);
cx.assert(
indoc! {"
Test tes|t
test"},
"Test tes|",
);
cx.assert(
indoc! {"
Test test
|
test"},
indoc! {"
Test test
|
test"},
);
let mut cx = cx.binding(["c", "shift-E"]);
cx.assert("Test te|st-test test", "Test te| test");
}
#[gpui::test]
async fn test_change_b(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "b"]).mode_after(Mode::Insert);
cx.assert("Te|st Test", "|st Test");
cx.assert("Test |test", "|test");
cx.assert("Test1 test2 |test3", "Test1 |test3");
cx.assert(
indoc! {"
Test test
|test"},
indoc! {"
Test |
test"},
);
cx.assert(
indoc! {"
Test test
|
test"},
indoc! {"
Test |
test"},
);
let mut cx = cx.binding(["c", "shift-B"]);
cx.assert("Test test-test |test", "Test |test");
}
#[gpui::test]
async fn test_change_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "shift-$"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The q|uick
brown fox"},
indoc! {"
The q|
brown fox"},
);
cx.assert(
indoc! {"
The quick
|
brown fox"},
indoc! {"
The quick
|
brown fox"},
);
}
#[gpui::test]
async fn test_change_0(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "0"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The q|uick
brown fox"},
indoc! {"
|uick
brown fox"},
);
cx.assert(
indoc! {"
The quick
|
brown fox"},
indoc! {"
The quick
|
brown fox"},
);
}
#[gpui::test]
async fn test_change_k(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "k"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The quick
brown |fox
jumps over"},
indoc! {"
|
jumps over"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps |over"},
indoc! {"
The quick
|"},
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over"},
indoc! {"
|
brown fox
jumps over"},
);
cx.assert(
indoc! {"
|
brown fox
jumps over"},
indoc! {"
|
brown fox
jumps over"},
);
}
#[gpui::test]
async fn test_change_j(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "j"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The quick
brown |fox
jumps over"},
indoc! {"
The quick
|"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps |over"},
indoc! {"
The quick
brown fox
|"},
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over"},
indoc! {"
|
jumps over"},
);
cx.assert(
indoc! {"
The quick
brown fox
|"},
indoc! {"
The quick
brown fox
|"},
);
}
#[gpui::test]
async fn test_change_end_of_document(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "shift-G"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
indoc! {"
The quick
|"},
);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
indoc! {"
The quick
|"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
the l|azy"},
indoc! {"
The quick
brown fox
jumps over
|"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
|"},
indoc! {"
The quick
brown fox
jumps over
|"},
);
}
#[gpui::test]
async fn test_change_gg(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["c", "g", "g"]).mode_after(Mode::Insert);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
indoc! {"
|
jumps over
the lazy"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
the l|azy"},
"|",
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over
the lazy"},
indoc! {"
|
brown fox
jumps over
the lazy"},
);
cx.assert(
indoc! {"
|
brown fox
jumps over
the lazy"},
indoc! {"
|
brown fox
jumps over
the lazy"},
);
}
}

View File

@@ -0,0 +1,386 @@
use crate::{motion::Motion, Vim};
use editor::Bias;
use gpui::MutableAppContext;
use language::SelectionGoal;
pub fn delete_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.move_selections(cx, |map, selection| {
let original_head = selection.head();
motion.expand_selection(map, selection, true);
selection.goal = SelectionGoal::Column(original_head.column());
});
editor.insert(&"", cx);
// Fixup cursor position after the deletion
editor.set_clip_at_line_ends(true, cx);
editor.move_cursors(cx, |map, mut cursor, goal| {
if motion.linewise() {
if let SelectionGoal::Column(column) = goal {
*cursor.column_mut() = column
}
}
(map.clip_point(cursor, Bias::Left), SelectionGoal::None)
});
});
});
}
#[cfg(test)]
mod test {
use indoc::indoc;
use crate::vim_test_context::VimTestContext;
#[gpui::test]
async fn test_delete_h(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "h"]);
cx.assert("Te|st", "T|st");
cx.assert("T|est", "|est");
cx.assert("|Test", "|Test");
cx.assert(
indoc! {"
Test
|test"},
indoc! {"
Test
|test"},
);
}
#[gpui::test]
async fn test_delete_l(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "l"]);
cx.assert("|Test", "|est");
cx.assert("Te|st", "Te|t");
cx.assert("Tes|t", "Te|s");
cx.assert(
indoc! {"
Tes|t
test"},
indoc! {"
Te|s
test"},
);
}
#[gpui::test]
async fn test_delete_w(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "w"]);
cx.assert("Te|st", "T|e");
cx.assert("T|est test", "T|test");
cx.assert(
indoc! {"
Test te|st
test"},
indoc! {"
Test t|e
test"},
);
cx.assert(
indoc! {"
Test tes|t
test"},
indoc! {"
Test te|s
test"},
);
cx.assert(
indoc! {"
Test test
|
test"},
indoc! {"
Test test
|
test"},
);
let mut cx = cx.binding(["d", "shift-W"]);
cx.assert("Test te|st-test test", "Test te|test");
}
#[gpui::test]
async fn test_delete_e(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "e"]);
cx.assert("Te|st Test", "Te| Test");
cx.assert("T|est test", "T| test");
cx.assert(
indoc! {"
Test te|st
test"},
indoc! {"
Test t|e
test"},
);
cx.assert(
indoc! {"
Test tes|t
test"},
"Test te|s",
);
cx.assert(
indoc! {"
Test test
|
test"},
indoc! {"
Test test
|
test"},
);
let mut cx = cx.binding(["d", "shift-E"]);
cx.assert("Test te|st-test test", "Test te| test");
}
#[gpui::test]
async fn test_delete_b(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "b"]);
cx.assert("Te|st Test", "|st Test");
cx.assert("Test |test", "|test");
cx.assert("Test1 test2 |test3", "Test1 |test3");
cx.assert(
indoc! {"
Test test
|test"},
// Trailing whitespace after cursor
indoc! {"
Test|
test"},
);
cx.assert(
indoc! {"
Test test
|
test"},
// Trailing whitespace after cursor
indoc! {"
Test|
test"},
);
let mut cx = cx.binding(["d", "shift-B"]);
cx.assert("Test test-test |test", "Test |test");
}
#[gpui::test]
async fn test_delete_end_of_line(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "shift-$"]);
cx.assert(
indoc! {"
The q|uick
brown fox"},
indoc! {"
The |q
brown fox"},
);
cx.assert(
indoc! {"
The quick
|
brown fox"},
indoc! {"
The quick
|
brown fox"},
);
}
#[gpui::test]
async fn test_delete_0(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "0"]);
cx.assert(
indoc! {"
The q|uick
brown fox"},
indoc! {"
|uick
brown fox"},
);
cx.assert(
indoc! {"
The quick
|
brown fox"},
indoc! {"
The quick
|
brown fox"},
);
}
#[gpui::test]
async fn test_delete_k(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "k"]);
cx.assert(
indoc! {"
The quick
brown |fox
jumps over"},
"jumps |over",
);
cx.assert(
indoc! {"
The quick
brown fox
jumps |over"},
"The qu|ick",
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over"},
indoc! {"
brown| fox
jumps over"},
);
cx.assert(
indoc! {"
|brown fox
jumps over"},
"|jumps over",
);
}
#[gpui::test]
async fn test_delete_j(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "j"]);
cx.assert(
indoc! {"
The quick
brown |fox
jumps over"},
"The qu|ick",
);
cx.assert(
indoc! {"
The quick
brown fox
jumps |over"},
indoc! {"
The quick
brown |fox"},
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over"},
"jumps| over",
);
cx.assert(
indoc! {"
The quick
brown fox
|"},
indoc! {"
The quick
|brown fox"},
);
}
#[gpui::test]
async fn test_delete_end_of_document(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "shift-G"]);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
"The q|uick",
);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
"The q|uick",
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
the l|azy"},
indoc! {"
The quick
brown fox
jumps| over"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
|"},
indoc! {"
The quick
brown fox
|jumps over"},
);
}
#[gpui::test]
async fn test_delete_gg(cx: &mut gpui::TestAppContext) {
let cx = VimTestContext::new(cx, true).await;
let mut cx = cx.binding(["d", "g", "g"]);
cx.assert(
indoc! {"
The quick
brown| fox
jumps over
the lazy"},
indoc! {"
jumps| over
the lazy"},
);
cx.assert(
indoc! {"
The quick
brown fox
jumps over
the l|azy"},
"|",
);
cx.assert(
indoc! {"
The q|uick
brown fox
jumps over
the lazy"},
indoc! {"
brown| fox
jumps over
the lazy"},
);
cx.assert(
indoc! {"
|
brown fox
jumps over
the lazy"},
indoc! {"
|brown fox
jumps over
the lazy"},
);
}
}

View File

@@ -1,10 +1,11 @@
#[cfg(test)]
mod vim_test_context;
mod editor_events;
mod insert;
mod motion;
mod normal;
mod state;
#[cfg(test)]
mod vim_test_context;
use collections::HashMap;
use editor::{CursorShape, Editor};
@@ -25,6 +26,7 @@ impl_actions!(vim, [SwitchMode, PushOperator]);
pub fn init(cx: &mut MutableAppContext) {
editor_events::init(cx);
normal::init(cx);
insert::init(cx);
motion::init(cx);
@@ -142,14 +144,14 @@ mod test {
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, false, "").await;
let mut cx = VimTestContext::new(cx, false).await;
cx.simulate_keystrokes(["h", "j", "k", "l"]);
cx.assert_editor_state("hjkl|");
}
#[gpui::test]
async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true, "").await;
let mut cx = VimTestContext::new(cx, true).await;
cx.simulate_keystroke("i");
assert_eq!(cx.mode(), Mode::Insert);

View File

@@ -15,11 +15,7 @@ pub struct VimTestContext<'a> {
}
impl<'a> VimTestContext<'a> {
pub async fn new(
cx: &'a mut gpui::TestAppContext,
enabled: bool,
initial_editor_text: &str,
) -> VimTestContext<'a> {
pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
cx.update(|cx| {
editor::init(cx);
crate::init(cx);
@@ -38,10 +34,7 @@ impl<'a> VimTestContext<'a> {
params
.fs
.as_fake()
.insert_tree(
"/root",
json!({ "dir": { "test.txt": initial_editor_text } }),
)
.insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
.await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
@@ -202,6 +195,14 @@ impl<'a> VimTestContext<'a> {
assert_eq!(self.mode(), mode_after);
assert_eq!(self.active_operator(), None);
}
pub fn binding<const COUNT: usize>(
mut self,
keystrokes: [&'static str; COUNT],
) -> VimBindingTestContext<'a, COUNT> {
let mode = self.mode();
VimBindingTestContext::new(keystrokes, mode, mode, self)
}
}
impl<'a> Deref for VimTestContext<'a> {
@@ -211,3 +212,61 @@ impl<'a> Deref for VimTestContext<'a> {
self.cx
}
}
pub struct VimBindingTestContext<'a, const COUNT: usize> {
cx: VimTestContext<'a>,
keystrokes_under_test: [&'static str; COUNT],
initial_mode: Mode,
mode_after: Mode,
}
impl<'a, const COUNT: usize> VimBindingTestContext<'a, COUNT> {
pub fn new(
keystrokes_under_test: [&'static str; COUNT],
initial_mode: Mode,
mode_after: Mode,
cx: VimTestContext<'a>,
) -> Self {
Self {
cx,
keystrokes_under_test,
initial_mode,
mode_after,
}
}
pub fn binding<const NEW_COUNT: usize>(
self,
keystrokes_under_test: [&'static str; NEW_COUNT],
) -> VimBindingTestContext<'a, NEW_COUNT> {
VimBindingTestContext {
keystrokes_under_test,
cx: self.cx,
initial_mode: self.initial_mode,
mode_after: self.mode_after,
}
}
pub fn mode_after(mut self, mode_after: Mode) -> Self {
self.mode_after = mode_after;
self
}
pub fn assert(&mut self, initial_state: &str, state_after: &str) {
self.cx.assert_binding(
self.keystrokes_under_test,
initial_state,
self.initial_mode,
state_after,
self.mode_after,
)
}
}
impl<'a, const COUNT: usize> Deref for VimBindingTestContext<'a, COUNT> {
type Target = VimTestContext<'a>;
fn deref(&self) -> &Self::Target {
&self.cx
}
}

View File

@@ -1297,7 +1297,7 @@ impl Workspace {
if project.is_local() {
if project.is_shared() {
project.unshare(cx);
} else {
} else if project.can_share(cx) {
project.share(cx).detach();
}
}
@@ -1475,28 +1475,38 @@ impl Workspace {
}
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
let mut worktree_root_names = String::new();
{
let mut worktrees = self.project.read(cx).visible_worktrees(cx).peekable();
while let Some(worktree) = worktrees.next() {
worktree_root_names.push_str(worktree.read(cx).root_name());
if worktrees.peek().is_some() {
worktree_root_names.push_str(", ");
}
}
}
ConstrainedBox::new(
Container::new(
Stack::new()
.with_child(
Align::new(
Label::new("zed".into(), theme.workspace.titlebar.title.clone())
.boxed(),
)
.boxed(),
Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
.aligned()
.left()
.boxed(),
)
.with_child(
Align::new(
Flex::row()
.with_children(self.render_share_icon(theme, cx))
.with_children(self.render_collaborators(theme, cx))
.with_child(self.render_current_user(
.with_children(self.render_current_user(
self.user_store.read(cx).current_user().as_ref(),
self.project.read(cx).replica_id(),
theme,
cx,
))
.with_children(self.render_connection_status(cx))
.with_children(self.render_share_icon(theme, cx))
.boxed(),
)
.right()
@@ -1540,25 +1550,30 @@ impl Workspace {
replica_id: ReplicaId,
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
) -> Option<ElementBox> {
let status = *self.client.status().borrow();
if let Some(avatar) = user.and_then(|user| user.avatar.clone()) {
self.render_avatar(avatar, replica_id, None, theme, cx)
Some(self.render_avatar(avatar, replica_id, None, theme, cx))
} else if matches!(status, client::Status::UpgradeRequired) {
None
} else {
MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
let style = if state.hovered {
&theme.workspace.titlebar.hovered_sign_in_prompt
} else {
&theme.workspace.titlebar.sign_in_prompt
};
Label::new("Sign in".to_string(), style.text.clone())
.contained()
.with_style(style.container)
.boxed()
})
.on_click(|cx| cx.dispatch_action(Authenticate))
.with_cursor_style(CursorStyle::PointingHand)
.aligned()
.boxed()
Some(
MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
let style = if state.hovered {
&theme.workspace.titlebar.hovered_sign_in_prompt
} else {
&theme.workspace.titlebar.sign_in_prompt
};
Label::new("Sign in".to_string(), style.text.clone())
.contained()
.with_style(style.container)
.boxed()
})
.on_click(|cx| cx.dispatch_action(Authenticate))
.with_cursor_style(CursorStyle::PointingHand)
.aligned()
.boxed(),
)
}
}
@@ -1598,6 +1613,8 @@ impl Workspace {
)
.constrained()
.with_width(theme.workspace.right_sidebar.width)
.contained()
.with_margin_left(2.)
.boxed();
if let Some(peer_id) = peer_id {
@@ -1611,22 +1628,39 @@ impl Workspace {
}
fn render_share_icon(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
if self.project().read(cx).is_local() && self.client.user_id().is_some() {
let color = if self.project().read(cx).is_shared() {
theme.workspace.titlebar.share_icon_active_color
} else {
theme.workspace.titlebar.share_icon_color
};
if self.project().read(cx).is_local()
&& self.client.user_id().is_some()
&& self.project().read(cx).can_share(cx)
{
Some(
MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |_, _| {
Align::new(
Svg::new("icons/broadcast-24.svg")
.with_color(color)
.constrained()
.with_width(24.)
.boxed(),
)
.boxed()
MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |state, cx| {
let style = if self.project().read(cx).is_shared() {
if state.hovered {
&theme.workspace.titlebar.hovered_active_share_icon
} else {
&theme.workspace.titlebar.active_share_icon
}
} else {
if state.hovered {
&theme.workspace.titlebar.hovered_share_icon
} else {
&theme.workspace.titlebar.share_icon
}
};
Svg::new("icons/share.svg")
.with_color(style.color)
.constrained()
.with_height(14.)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(24.)
.aligned()
.constrained()
.with_width(theme.workspace.right_sidebar.width)
.aligned()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(|cx| cx.dispatch_action(ToggleShare))
@@ -1983,7 +2017,14 @@ impl View for Workspace {
content.add_child(self.right_sidebar.render(&theme, cx));
content.boxed()
})
.with_children(self.modal.as_ref().map(|m| ChildView::new(m).boxed()))
.with_children(self.modal.as_ref().map(|m| {
ChildView::new(m)
.contained()
.with_style(theme.workspace.modal)
.aligned()
.top()
.boxed()
}))
.flex(1.0, true)
.boxed(),
)

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.29.0"
version = "0.30.0"
[lib]
name = "zed"

View File

@@ -63,7 +63,7 @@ pub fn build_language_registry(login_shell_env_loaded: Task<()>) -> LanguageRegi
languages
}
fn language(
pub(crate) fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>,

View File

@@ -56,7 +56,16 @@
[
"."
";"
] @delimiter
] @punctuation.delimiter
[
"{"
"}"
"("
")"
"["
"]"
] @punctuation.bracket
[
(string_literal)

View File

@@ -86,6 +86,7 @@
"?."
"."
","
":"
] @punctuation.delimiter
[

View File

@@ -11,4 +11,11 @@
(true)
(false)
(null)
] @constant
] @constant
[
"{"
"}"
"["
"]"
] @punctuation.bracket

View File

@@ -12,7 +12,7 @@
(list_marker_star)
(list_marker_dot)
(list_marker_parenthesis)
] @list_marker
] @punctuation.list_marker
[
(indented_code_block)

View File

@@ -34,6 +34,20 @@
((identifier) @constant
(#match? @constant "^[A-Z][A-Z\\d_]+$"))
[
"("
")"
"{"
"}"
"["
"]"
] @punctuation.bracket
(_
.
"<" @punctuation.bracket
">" @punctuation.bracket)
[
"as"
"async"

View File

@@ -20,14 +20,18 @@
; Punctuation
;------------
"." @punctuation.delimiter
"," @punctuation.delimiter
[
"."
","
] @punctuation.delimiter
"=" @operator
"[" @punctuation.bracket
"]" @punctuation.bracket
"[[" @punctuation.bracket
"]]" @punctuation.bracket
"{" @punctuation.bracket
"}" @punctuation.bracket
[
"["
"]"
"[["
"]]"
"{"
"}"
] @punctuation.bracket

View File

@@ -144,3 +144,54 @@ impl LspAdapter for TypeScriptLspAdapter {
}))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use gpui::MutableAppContext;
use unindent::Unindent;
#[gpui::test]
fn test_outline(cx: &mut MutableAppContext) {
let language = crate::languages::language(
"typescript",
tree_sitter_typescript::language_typescript(),
None,
);
let text = r#"
function a() {
// local variables are omitted
let a1 = 1;
// all functions are included
async function a2() {}
}
// top-level variables are included
let b: C
function getB() {}
// exported variables are included
export const d = e;
"#
.unindent();
let buffer = cx.add_model(|cx| {
language::Buffer::new(0, text, cx).with_language(Arc::new(language), cx)
});
let outline = buffer.read(cx).snapshot().outline(None).unwrap();
assert_eq!(
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>(),
&[
("function a ( )", 0),
("async function a2 ( )", 1),
("let b", 0),
("function getB ( )", 0),
("const d", 0),
]
);
}
}

View File

@@ -86,6 +86,7 @@
"?."
"."
","
":"
] @punctuation.delimiter
[

View File

@@ -6,6 +6,10 @@
"enum" @context
name: (_) @name) @item
(type_alias_declaration
"type" @context
name: (_) @name) @item
(function_declaration
"async"? @context
"function" @context
@@ -18,6 +22,12 @@
"interface" @context
name: (_) @name) @item
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (_) @name) @item))
(program
(lexical_declaration
["let" "const"] @context

View File

@@ -169,11 +169,19 @@ fn main() {
.detach();
languages.set_language_server_download_dir(zed::ROOT_PATH.clone());
languages.set_theme(&settings.theme.editor.syntax);
let languages = Arc::new(languages);
cx.observe_global::<Settings, _>({
let languages = languages.clone();
move |settings, _| {
languages.set_theme(&settings.theme.editor.syntax);
}
})
.detach();
cx.set_global(settings);
let app_state = Arc::new(AppState {
languages: Arc::new(languages),
languages,
themes,
channel_list,
client,

567
styles/dist/solarized-dark.json vendored Normal file
View File

@@ -0,0 +1,567 @@
{
"meta": {
"themeName": "solarized-dark"
},
"text": {
"primary": {
"value": "#eee8d5",
"type": "color"
},
"secondary": {
"value": "#93a1a1",
"type": "color"
},
"muted": {
"value": "#93a1a1",
"type": "color"
},
"placeholder": {
"value": "#839496",
"type": "color"
},
"active": {
"value": "#fdf6e3",
"type": "color"
},
"feature": {
"value": "#268bd2",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"icon": {
"primary": {
"value": "#eee8d5",
"type": "color"
},
"secondary": {
"value": "#93a1a1",
"type": "color"
},
"muted": {
"value": "#93a1a1",
"type": "color"
},
"placeholder": {
"value": "#839496",
"type": "color"
},
"active": {
"value": "#fdf6e3",
"type": "color"
},
"feature": {
"value": "#268bd2",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"background": {
"100": {
"base": {
"value": "#073642",
"type": "color"
},
"hovered": {
"value": "#586e75",
"type": "color"
},
"active": {
"value": "#586e75",
"type": "color"
},
"focused": {
"value": "#586e75",
"type": "color"
}
},
"300": {
"base": {
"value": "#073642",
"type": "color"
},
"hovered": {
"value": "#586e75",
"type": "color"
},
"active": {
"value": "#586e75",
"type": "color"
},
"focused": {
"value": "#586e75",
"type": "color"
}
},
"500": {
"base": {
"value": "#002b36",
"type": "color"
},
"hovered": {
"value": "#073642",
"type": "color"
},
"active": {
"value": "#073642",
"type": "color"
},
"focused": {
"value": "#073642",
"type": "color"
}
},
"on300": {
"base": {
"value": "#002b36",
"type": "color"
},
"hovered": {
"value": "#073642",
"type": "color"
},
"active": {
"value": "#073642",
"type": "color"
},
"focused": {
"value": "#073642",
"type": "color"
}
},
"on500": {
"base": {
"value": "#073642",
"type": "color"
},
"hovered": {
"value": "#586e75",
"type": "color"
},
"active": {
"value": "#586e75",
"type": "color"
},
"focused": {
"value": "#586e75",
"type": "color"
}
},
"ok": {
"base": {
"value": "#859900",
"type": "color"
},
"hovered": {
"value": "#859900",
"type": "color"
},
"active": {
"value": "#859900",
"type": "color"
},
"focused": {
"value": "#859900",
"type": "color"
}
},
"error": {
"base": {
"value": "#dc322f",
"type": "color"
},
"hovered": {
"value": "#dc322f",
"type": "color"
},
"active": {
"value": "#dc322f",
"type": "color"
},
"focused": {
"value": "#dc322f",
"type": "color"
}
},
"warning": {
"base": {
"value": "#b58900",
"type": "color"
},
"hovered": {
"value": "#b58900",
"type": "color"
},
"active": {
"value": "#b58900",
"type": "color"
},
"focused": {
"value": "#b58900",
"type": "color"
}
},
"info": {
"base": {
"value": "#268bd2",
"type": "color"
},
"hovered": {
"value": "#268bd2",
"type": "color"
},
"active": {
"value": "#268bd2",
"type": "color"
},
"focused": {
"value": "#268bd2",
"type": "color"
}
}
},
"border": {
"primary": {
"value": "#002b36",
"type": "color"
},
"secondary": {
"value": "#073642",
"type": "color"
},
"muted": {
"value": "#586e75",
"type": "color"
},
"focused": {
"value": "#586e75",
"type": "color"
},
"active": {
"value": "#586e75",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"editor": {
"background": {
"value": "#002b36",
"type": "color"
},
"indent_guide": {
"value": "#586e75",
"type": "color"
},
"indent_guide_active": {
"value": "#073642",
"type": "color"
},
"line": {
"active": {
"value": "#fdf6e312",
"type": "color"
},
"highlighted": {
"value": "#fdf6e31f",
"type": "color"
},
"inserted": {
"value": "#859900",
"type": "color"
},
"deleted": {
"value": "#dc322f",
"type": "color"
},
"modified": {
"value": "#268bd2",
"type": "color"
}
},
"highlight": {
"selection": {
"value": "#268bd23d",
"type": "color"
},
"occurrence": {
"value": "#657b831f",
"type": "color"
},
"activeOccurrence": {
"value": "#657b8329",
"type": "color"
},
"matchingBracket": {
"value": "#073642",
"type": "color"
},
"match": {
"value": "#6c71c480",
"type": "color"
},
"activeMatch": {
"value": "#6c71c4b3",
"type": "color"
},
"related": {
"value": "#073642",
"type": "color"
}
},
"gutter": {
"primary": {
"value": "#839496",
"type": "color"
},
"active": {
"value": "#fdf6e3",
"type": "color"
}
}
},
"syntax": {
"primary": {
"value": "#fdf6e3",
"type": "color"
},
"comment": {
"value": "#eee8d5",
"type": "color"
},
"keyword": {
"value": "#268bd2",
"type": "color"
},
"function": {
"value": "#b58900",
"type": "color"
},
"type": {
"value": "#2aa198",
"type": "color"
},
"variant": {
"value": "#268bd2",
"type": "color"
},
"property": {
"value": "#268bd2",
"type": "color"
},
"enum": {
"value": "#cb4b16",
"type": "color"
},
"operator": {
"value": "#cb4b16",
"type": "color"
},
"string": {
"value": "#cb4b16",
"type": "color"
},
"number": {
"value": "#859900",
"type": "color"
},
"boolean": {
"value": "#859900",
"type": "color"
}
},
"player": {
"1": {
"baseColor": {
"value": "#268bd2",
"type": "color"
},
"cursorColor": {
"value": "#268bd2",
"type": "color"
},
"selectionColor": {
"value": "#268bd23d",
"type": "color"
},
"borderColor": {
"value": "#268bd2cc",
"type": "color"
}
},
"2": {
"baseColor": {
"value": "#859900",
"type": "color"
},
"cursorColor": {
"value": "#859900",
"type": "color"
},
"selectionColor": {
"value": "#8599003d",
"type": "color"
},
"borderColor": {
"value": "#859900cc",
"type": "color"
}
},
"3": {
"baseColor": {
"value": "#d33682",
"type": "color"
},
"cursorColor": {
"value": "#d33682",
"type": "color"
},
"selectionColor": {
"value": "#d336823d",
"type": "color"
},
"borderColor": {
"value": "#d33682cc",
"type": "color"
}
},
"4": {
"baseColor": {
"value": "#cb4b16",
"type": "color"
},
"cursorColor": {
"value": "#cb4b16",
"type": "color"
},
"selectionColor": {
"value": "#cb4b163d",
"type": "color"
},
"borderColor": {
"value": "#cb4b16cc",
"type": "color"
}
},
"5": {
"baseColor": {
"value": "#6c71c4",
"type": "color"
},
"cursorColor": {
"value": "#6c71c4",
"type": "color"
},
"selectionColor": {
"value": "#6c71c43d",
"type": "color"
},
"borderColor": {
"value": "#6c71c4cc",
"type": "color"
}
},
"6": {
"baseColor": {
"value": "#2aa198",
"type": "color"
},
"cursorColor": {
"value": "#2aa198",
"type": "color"
},
"selectionColor": {
"value": "#2aa1983d",
"type": "color"
},
"borderColor": {
"value": "#2aa198cc",
"type": "color"
}
},
"7": {
"baseColor": {
"value": "#dc322f",
"type": "color"
},
"cursorColor": {
"value": "#dc322f",
"type": "color"
},
"selectionColor": {
"value": "#dc322f3d",
"type": "color"
},
"borderColor": {
"value": "#dc322fcc",
"type": "color"
}
},
"8": {
"baseColor": {
"value": "#b58900",
"type": "color"
},
"cursorColor": {
"value": "#b58900",
"type": "color"
},
"selectionColor": {
"value": "#b589003d",
"type": "color"
},
"borderColor": {
"value": "#b58900cc",
"type": "color"
}
}
},
"shadowAlpha": {
"value": 0.32,
"type": "number"
}
}

567
styles/dist/solarized-light.json vendored Normal file
View File

@@ -0,0 +1,567 @@
{
"meta": {
"themeName": "solarized-light"
},
"text": {
"primary": {
"value": "#073642",
"type": "color"
},
"secondary": {
"value": "#586e75",
"type": "color"
},
"muted": {
"value": "#586e75",
"type": "color"
},
"placeholder": {
"value": "#657b83",
"type": "color"
},
"active": {
"value": "#002b36",
"type": "color"
},
"feature": {
"value": "#268bd2",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"icon": {
"primary": {
"value": "#073642",
"type": "color"
},
"secondary": {
"value": "#586e75",
"type": "color"
},
"muted": {
"value": "#586e75",
"type": "color"
},
"placeholder": {
"value": "#657b83",
"type": "color"
},
"active": {
"value": "#002b36",
"type": "color"
},
"feature": {
"value": "#268bd2",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"background": {
"100": {
"base": {
"value": "#eee8d5",
"type": "color"
},
"hovered": {
"value": "#93a1a1",
"type": "color"
},
"active": {
"value": "#93a1a1",
"type": "color"
},
"focused": {
"value": "#93a1a1",
"type": "color"
}
},
"300": {
"base": {
"value": "#eee8d5",
"type": "color"
},
"hovered": {
"value": "#93a1a1",
"type": "color"
},
"active": {
"value": "#93a1a1",
"type": "color"
},
"focused": {
"value": "#93a1a1",
"type": "color"
}
},
"500": {
"base": {
"value": "#fdf6e3",
"type": "color"
},
"hovered": {
"value": "#eee8d5",
"type": "color"
},
"active": {
"value": "#eee8d5",
"type": "color"
},
"focused": {
"value": "#eee8d5",
"type": "color"
}
},
"on300": {
"base": {
"value": "#fdf6e3",
"type": "color"
},
"hovered": {
"value": "#eee8d5",
"type": "color"
},
"active": {
"value": "#eee8d5",
"type": "color"
},
"focused": {
"value": "#eee8d5",
"type": "color"
}
},
"on500": {
"base": {
"value": "#eee8d5",
"type": "color"
},
"hovered": {
"value": "#93a1a1",
"type": "color"
},
"active": {
"value": "#93a1a1",
"type": "color"
},
"focused": {
"value": "#93a1a1",
"type": "color"
}
},
"ok": {
"base": {
"value": "#859900",
"type": "color"
},
"hovered": {
"value": "#859900",
"type": "color"
},
"active": {
"value": "#859900",
"type": "color"
},
"focused": {
"value": "#859900",
"type": "color"
}
},
"error": {
"base": {
"value": "#dc322f",
"type": "color"
},
"hovered": {
"value": "#dc322f",
"type": "color"
},
"active": {
"value": "#dc322f",
"type": "color"
},
"focused": {
"value": "#dc322f",
"type": "color"
}
},
"warning": {
"base": {
"value": "#b58900",
"type": "color"
},
"hovered": {
"value": "#b58900",
"type": "color"
},
"active": {
"value": "#b58900",
"type": "color"
},
"focused": {
"value": "#b58900",
"type": "color"
}
},
"info": {
"base": {
"value": "#268bd2",
"type": "color"
},
"hovered": {
"value": "#268bd2",
"type": "color"
},
"active": {
"value": "#268bd2",
"type": "color"
},
"focused": {
"value": "#268bd2",
"type": "color"
}
}
},
"border": {
"primary": {
"value": "#fdf6e3",
"type": "color"
},
"secondary": {
"value": "#eee8d5",
"type": "color"
},
"muted": {
"value": "#93a1a1",
"type": "color"
},
"focused": {
"value": "#93a1a1",
"type": "color"
},
"active": {
"value": "#93a1a1",
"type": "color"
},
"ok": {
"value": "#859900",
"type": "color"
},
"error": {
"value": "#dc322f",
"type": "color"
},
"warning": {
"value": "#b58900",
"type": "color"
},
"info": {
"value": "#268bd2",
"type": "color"
}
},
"editor": {
"background": {
"value": "#fdf6e3",
"type": "color"
},
"indent_guide": {
"value": "#93a1a1",
"type": "color"
},
"indent_guide_active": {
"value": "#eee8d5",
"type": "color"
},
"line": {
"active": {
"value": "#002b3612",
"type": "color"
},
"highlighted": {
"value": "#002b361f",
"type": "color"
},
"inserted": {
"value": "#859900",
"type": "color"
},
"deleted": {
"value": "#dc322f",
"type": "color"
},
"modified": {
"value": "#268bd2",
"type": "color"
}
},
"highlight": {
"selection": {
"value": "#268bd23d",
"type": "color"
},
"occurrence": {
"value": "#8394961f",
"type": "color"
},
"activeOccurrence": {
"value": "#83949629",
"type": "color"
},
"matchingBracket": {
"value": "#eee8d5",
"type": "color"
},
"match": {
"value": "#6c71c480",
"type": "color"
},
"activeMatch": {
"value": "#6c71c4b3",
"type": "color"
},
"related": {
"value": "#eee8d5",
"type": "color"
}
},
"gutter": {
"primary": {
"value": "#657b83",
"type": "color"
},
"active": {
"value": "#002b36",
"type": "color"
}
}
},
"syntax": {
"primary": {
"value": "#002b36",
"type": "color"
},
"comment": {
"value": "#073642",
"type": "color"
},
"keyword": {
"value": "#268bd2",
"type": "color"
},
"function": {
"value": "#b58900",
"type": "color"
},
"type": {
"value": "#2aa198",
"type": "color"
},
"variant": {
"value": "#268bd2",
"type": "color"
},
"property": {
"value": "#268bd2",
"type": "color"
},
"enum": {
"value": "#cb4b16",
"type": "color"
},
"operator": {
"value": "#cb4b16",
"type": "color"
},
"string": {
"value": "#cb4b16",
"type": "color"
},
"number": {
"value": "#859900",
"type": "color"
},
"boolean": {
"value": "#859900",
"type": "color"
}
},
"player": {
"1": {
"baseColor": {
"value": "#268bd2",
"type": "color"
},
"cursorColor": {
"value": "#268bd2",
"type": "color"
},
"selectionColor": {
"value": "#268bd23d",
"type": "color"
},
"borderColor": {
"value": "#268bd2cc",
"type": "color"
}
},
"2": {
"baseColor": {
"value": "#859900",
"type": "color"
},
"cursorColor": {
"value": "#859900",
"type": "color"
},
"selectionColor": {
"value": "#8599003d",
"type": "color"
},
"borderColor": {
"value": "#859900cc",
"type": "color"
}
},
"3": {
"baseColor": {
"value": "#d33682",
"type": "color"
},
"cursorColor": {
"value": "#d33682",
"type": "color"
},
"selectionColor": {
"value": "#d336823d",
"type": "color"
},
"borderColor": {
"value": "#d33682cc",
"type": "color"
}
},
"4": {
"baseColor": {
"value": "#cb4b16",
"type": "color"
},
"cursorColor": {
"value": "#cb4b16",
"type": "color"
},
"selectionColor": {
"value": "#cb4b163d",
"type": "color"
},
"borderColor": {
"value": "#cb4b16cc",
"type": "color"
}
},
"5": {
"baseColor": {
"value": "#6c71c4",
"type": "color"
},
"cursorColor": {
"value": "#6c71c4",
"type": "color"
},
"selectionColor": {
"value": "#6c71c43d",
"type": "color"
},
"borderColor": {
"value": "#6c71c4cc",
"type": "color"
}
},
"6": {
"baseColor": {
"value": "#2aa198",
"type": "color"
},
"cursorColor": {
"value": "#2aa198",
"type": "color"
},
"selectionColor": {
"value": "#2aa1983d",
"type": "color"
},
"borderColor": {
"value": "#2aa198cc",
"type": "color"
}
},
"7": {
"baseColor": {
"value": "#dc322f",
"type": "color"
},
"cursorColor": {
"value": "#dc322f",
"type": "color"
},
"selectionColor": {
"value": "#dc322f3d",
"type": "color"
},
"borderColor": {
"value": "#dc322fcc",
"type": "color"
}
},
"8": {
"baseColor": {
"value": "#b58900",
"type": "color"
},
"cursorColor": {
"value": "#b58900",
"type": "color"
},
"selectionColor": {
"value": "#b589003d",
"type": "color"
},
"borderColor": {
"value": "#b58900cc",
"type": "color"
}
}
},
"shadowAlpha": {
"value": 0.32,
"type": "number"
}
}

View File

@@ -1,11 +1,20 @@
import * as fs from "fs";
import * as path from "path";
import app from "./styleTree/app";
import { dark as caveDark, light as caveLight } from "./themes/cave";
import dark from "./themes/dark";
import light from "./themes/light";
import { dark as solarizedDark, light as solarizedLight } from "./themes/solarized";
import { dark as sulphurpoolDark, light as sulphurpoolLight } from "./themes/sulphurpool";
import snakeCase from "./utils/snakeCase";
const themes = [dark, light];
const themes = [
dark, light,
caveDark, caveLight,
solarizedDark, solarizedLight,
sulphurpoolDark, sulphurpoolLight
];
for (let theme of themes) {
let styleTree = snakeCase(app(theme));
let styleTreeJSON = JSON.stringify(styleTree, null, 2);

View File

@@ -37,8 +37,18 @@ export default function editor(theme: Theme) {
};
}
const syntax: any = {};
for (const syntaxKey in theme.syntax) {
const style = theme.syntax[syntaxKey];
syntax[syntaxKey] = {
color: style.color.value,
weight: style.weight.value,
underline: style.underline,
italic: style.italic,
};
}
return {
// textColor: theme.syntax.primary.color,
textColor: theme.syntax.primary.color.value,
background: backgroundColor(theme, 500),
activeLineBackground: theme.editor.line.active.value,
@@ -125,22 +135,6 @@ export default function editor(theme: Theme) {
invalidHintDiagnostic: diagnostic(theme, "muted"),
invalidInformationDiagnostic: diagnostic(theme, "muted"),
invalidWarningDiagnostic: diagnostic(theme, "muted"),
syntax: {
keyword: theme.syntax.keyword.color.value,
function: theme.syntax.function.color.value,
string: theme.syntax.string.color.value,
type: theme.syntax.type.color.value,
number: theme.syntax.number.color.value,
comment: theme.syntax.comment.color.value,
property: theme.syntax.property.color.value,
variant: theme.syntax.variant.color.value,
constant: theme.syntax.constant.color.value,
title: { color: theme.syntax.title.color.value, weight: "bold" },
emphasis: theme.textColor.feature.value,
"emphasis.strong": { color: theme.textColor.feature.value, weight: "bold" },
link_uri: { color: theme.syntax.linkUrl.color.value, underline: true },
link_text: { color: theme.syntax.linkText.color.value, italic: true },
list_marker: theme.syntax.punctuation.color.value,
},
syntax,
};
}

View File

@@ -50,10 +50,6 @@ export default function selectorModal(theme: Theme): Object {
top: 7,
},
},
margin: {
bottom: 52,
top: 52,
},
shadow: shadow(theme),
};
}

View File

@@ -68,6 +68,10 @@ export default function workspace(theme: Theme) {
},
},
};
const shareIcon = {
margin: { top: 3, bottom: 2 },
cornerRadius: 6,
};
return {
background: backgroundColor(theme, 300),
@@ -75,6 +79,13 @@ export default function workspace(theme: Theme) {
leaderBorderWidth: 2.0,
tab,
activeTab,
modal: {
margin: {
bottom: 52,
top: 52,
},
cursor: "Arrow"
},
leftSidebar: {
...sidebar,
border: border(theme, "primary", { right: true }),
@@ -105,8 +116,9 @@ export default function workspace(theme: Theme) {
avatarWidth: 18,
height: 32,
background: backgroundColor(theme, 100),
shareIconColor: iconColor(theme, "secondary"),
shareIconActiveColor: iconColor(theme, "feature"),
padding: {
left: 80,
},
title: text(theme, "sans", "primary"),
avatar: {
cornerRadius: 10,
@@ -134,9 +146,29 @@ export default function workspace(theme: Theme) {
right: 4,
},
},
shareIcon: {
...shareIcon,
color: iconColor(theme, "secondary")
},
hoveredShareIcon: {
...shareIcon,
background: backgroundColor(theme, 100, "hovered"),
color: iconColor(theme, "secondary"),
},
hoveredActiveShareIcon: {
...shareIcon,
background: backgroundColor(theme, 100, "hovered"),
color: iconColor(theme, "active"),
},
activeShareIcon: {
...shareIcon,
background: backgroundColor(theme, 100, "active"),
color: iconColor(theme, "active"),
},
outdatedWarning: {
...text(theme, "sans", "warning"),
size: 13,
margin: { right: 6 }
},
},
toolbar: {

242
styles/src/themes/base16.ts Normal file
View File

@@ -0,0 +1,242 @@
import { ColorToken, fontWeights, NumberToken } from "../tokens";
import { withOpacity } from "../utils/color";
import Theme, { buildPlayer, Syntax } from "./theme";
export interface Accents {
"red": ColorToken,
"orange": ColorToken,
"yellow": ColorToken,
"green": ColorToken,
"cyan": ColorToken,
"blue": ColorToken,
"violet": ColorToken,
"magenta": ColorToken,
}
export function createTheme(name: string, isLight: boolean, neutral: ColorToken[], accent: Accents): Theme {
if (isLight) {
neutral = [...neutral].reverse();
}
let blend = isLight ? 0.12 : 0.32;
const backgroundColor = {
100: {
base: neutral[1],
hovered: withOpacity(neutral[2], blend),
active: withOpacity(neutral[2], blend * 1.5),
focused: neutral[2],
},
300: {
base: neutral[1],
hovered: withOpacity(neutral[2], blend),
active: withOpacity(neutral[2], blend * 1.5),
focused: neutral[2],
},
500: {
base: neutral[0],
hovered: neutral[1],
active: neutral[1],
focused: neutral[1],
},
on300: {
base: neutral[0],
hovered: neutral[1],
active: neutral[1],
focused: neutral[1],
},
on500: {
base: neutral[1],
hovered: neutral[3],
active: neutral[3],
focused: neutral[3],
},
ok: {
base: accent.green,
hovered: accent.green,
active: accent.green,
focused: accent.green,
},
error: {
base: accent.red,
hovered: accent.red,
active: accent.red,
focused: accent.red,
},
warning: {
base: accent.yellow,
hovered: accent.yellow,
active: accent.yellow,
focused: accent.yellow,
},
info: {
base: accent.blue,
hovered: accent.blue,
active: accent.blue,
focused: accent.blue,
},
};
const borderColor = {
primary: neutral[0],
secondary: neutral[1],
muted: neutral[3],
focused: neutral[3],
active: neutral[3],
ok: accent.green,
error: accent.red,
warning: accent.yellow,
info: accent.blue,
};
const textColor = {
primary: neutral[6],
secondary: neutral[5],
muted: neutral[5],
placeholder: neutral[4],
active: neutral[7],
feature: accent.blue,
ok: accent.green,
error: accent.red,
warning: accent.yellow,
info: accent.blue,
};
const player = {
1: buildPlayer(accent.blue),
2: buildPlayer(accent.green),
3: buildPlayer(accent.magenta),
4: buildPlayer(accent.orange),
5: buildPlayer(accent.violet),
6: buildPlayer(accent.cyan),
7: buildPlayer(accent.red),
8: buildPlayer(accent.yellow),
};
const editor = {
background: backgroundColor[500].base,
indent_guide: borderColor.muted,
indent_guide_active: borderColor.secondary,
line: {
active: withOpacity(neutral[7], 0.07),
highlighted: withOpacity(neutral[7], 0.12),
inserted: backgroundColor.ok.active,
deleted: backgroundColor.error.active,
modified: backgroundColor.info.active,
},
highlight: {
selection: player[1].selectionColor,
occurrence: withOpacity(neutral[0], 0.12),
activeOccurrence: withOpacity(neutral[0], 0.16),
matchingBracket: backgroundColor[500].active,
match: withOpacity(accent.violet, 0.5),
activeMatch: withOpacity(accent.violet, 0.7),
related: backgroundColor[500].focused,
},
gutter: {
primary: textColor.placeholder,
active: textColor.active,
},
};
const syntax: Syntax = {
primary: {
color: neutral[7],
weight: fontWeights.normal,
},
comment: {
color: neutral[5],
weight: fontWeights.normal,
},
punctuation: {
color: neutral[5],
weight: fontWeights.normal,
},
constant: {
color: neutral[4],
weight: fontWeights.normal,
},
keyword: {
color: accent.blue,
weight: fontWeights.normal,
},
function: {
color: accent.yellow,
weight: fontWeights.normal,
},
type: {
color: accent.cyan,
weight: fontWeights.normal,
},
variant: {
color: accent.blue,
weight: fontWeights.normal,
},
property: {
color: accent.blue,
weight: fontWeights.normal,
},
enum: {
color: accent.orange,
weight: fontWeights.normal,
},
operator: {
color: accent.orange,
weight: fontWeights.normal,
},
string: {
color: accent.orange,
weight: fontWeights.normal,
},
number: {
color: accent.green,
weight: fontWeights.normal,
},
boolean: {
color: accent.green,
weight: fontWeights.normal,
},
predictive: {
color: textColor.muted,
weight: fontWeights.normal,
},
title: {
color: accent.yellow,
weight: fontWeights.bold,
},
emphasis: {
color: textColor.feature,
weight: fontWeights.normal,
},
"emphasis.strong": {
color: textColor.feature,
weight: fontWeights.bold,
},
linkUri: {
color: accent.green,
weight: fontWeights.normal,
underline: true,
},
linkText: {
color: accent.orange,
weight: fontWeights.normal,
italic: true,
},
};
const shadowAlpha: NumberToken = {
value: blend,
type: "number",
};
return {
name,
backgroundColor,
borderColor,
textColor,
iconColor: textColor,
editor,
syntax,
player,
shadowAlpha,
};
}

29
styles/src/themes/cave.ts Normal file
View File

@@ -0,0 +1,29 @@
import { createTheme } from "./base16";
import { color } from "../tokens";
const name = "cave";
const neutrals = [
color("#19171c"),
color("#26232a"),
color("#585260"),
color("#655f6d"),
color("#7e7887"),
color("#8b8792"),
color("#e2dfe7"),
color("#efecf4"),
];
const colors = {
"red": color("#be4678"),
"orange": color("#aa573c"),
"yellow": color("#a06e3b"),
"green": color("#2a9292"),
"cyan": color("#398bc6"),
"blue": color("#576ddb"),
"violet": color("#955ae7"),
"magenta": color("#bf40bf"),
};
export const dark = createTheme(`${name}-dark`, false, neutrals, colors);
export const light = createTheme(`${name}-light`, true, neutrals, colors);

View File

@@ -1,4 +1,4 @@
import { colors, fontWeights, NumberToken } from "../tokens";
import { color, colors, fontWeights, NumberToken } from "../tokens";
import { withOpacity } from "../utils/color";
import Theme, { buildPlayer, Syntax } from "./theme";
@@ -202,22 +202,22 @@ const syntax: Syntax = {
weight: fontWeights.bold,
},
emphasis: {
color: textColor.active,
color: textColor.feature,
weight: fontWeights.normal,
},
emphasisStrong: {
color: textColor.active,
"emphasis.strong": {
color: textColor.feature,
weight: fontWeights.bold,
},
linkUrl: {
linkUri: {
color: colors.lime[500],
weight: fontWeights.normal,
// TODO: add underline
underline: true,
},
linkText: {
color: colors.orange[500],
weight: fontWeights.normal,
// TODO: add italic
italic: true,
},
};

View File

@@ -200,22 +200,22 @@ const syntax: Syntax = {
weight: fontWeights.bold,
},
emphasis: {
color: textColor.active,
color: textColor.feature,
weight: fontWeights.normal,
},
emphasisStrong: {
color: textColor.active,
"emphasis.strong": {
color: textColor.feature,
weight: fontWeights.bold,
},
linkUrl: {
linkUri: {
color: colors.lime[500],
weight: fontWeights.normal,
// TODO: add underline
underline: true
},
linkText: {
color: colors.red[500],
weight: fontWeights.normal,
// TODO: add italic
italic: true
},
};

View File

@@ -0,0 +1,29 @@
import { createTheme } from "./base16";
import { color } from "../tokens";
const name = "solarized";
const neutrals = [
color("#002b36"),
color("#073642"),
color("#586e75"),
color("#657b83"),
color("#839496"),
color("#93a1a1"),
color("#eee8d5"),
color("#fdf6e3"),
];
const colors = {
"red": color("#dc322f"),
"orange": color("#cb4b16"),
"yellow": color("#b58900"),
"green": color("#859900"),
"cyan": color("#2aa198"),
"blue": color("#268bd2"),
"violet": color("#6c71c4"),
"magenta": color("#d33682"),
};
export const dark = createTheme(`${name}-dark`, false, neutrals, colors);
export const light = createTheme(`${name}-light`, true, neutrals, colors);

View File

@@ -0,0 +1,29 @@
import { createTheme } from "./base16";
import { color } from "../tokens";
const name = "sulphurpool";
const neutrals = [
color("#202746"),
color("#293256"),
color("#5e6687"),
color("#6b7394"),
color("#898ea4"),
color("#979db4"),
color("#dfe2f1"),
color("#f5f7ff"),
]
const colors = {
"red": color("#c94922"),
"orange": color("#c76b29"),
"yellow": color("#c08b30"),
"green": color("#ac9739"),
"cyan": color("#22a2c9"),
"blue": color("#3d8fd1"),
"violet": color("#6679cc"),
"magenta": color("#9c637a"),
};
export const dark = createTheme(`${name}-dark`, false, neutrals, colors);
export const light = createTheme(`${name}-light`, true, neutrals, colors);

View File

@@ -3,7 +3,9 @@ import { withOpacity } from "../utils/color";
export interface SyntaxHighlightStyle {
color: ColorToken;
weight: FontWeightToken;
weight?: FontWeightToken;
underline?: boolean,
italic?: boolean,
}
export interface Player {
@@ -49,21 +51,30 @@ export interface Syntax {
number: SyntaxHighlightStyle;
boolean: SyntaxHighlightStyle;
predictive: SyntaxHighlightStyle;
// TODO: Either move the following or rename
title: SyntaxHighlightStyle;
emphasis: SyntaxHighlightStyle;
emphasisStrong: SyntaxHighlightStyle;
linkUrl: SyntaxHighlightStyle;
linkUri: SyntaxHighlightStyle;
linkText: SyntaxHighlightStyle;
[key: string]: SyntaxHighlightStyle;
};
export default interface Theme {
name: string;
backgroundColor: {
// Basically just Title Bar
// Lowest background level
100: BackgroundColorSet;
// Tab bars, panels, popovers
// Mid-ground
300: BackgroundColorSet;
// The editor
// Foreground
500: BackgroundColorSet;
// Hacks for elements on top of the midground
// Buttons in a panel, tab bar, or panel
on300: BackgroundColorSet;
// Hacks for elements on top of the editor
on500: BackgroundColorSet;
ok: BackgroundColorSet;
error: BackgroundColorSet;

View File

@@ -71,6 +71,12 @@ export interface ColorToken {
type: "color",
step?: number,
}
export function color(value: string): ColorToken {
return {
value,
type: "color",
};
}
export const colors = {
neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1
rose: colorRamp("#F43F5EFF"),

View File

@@ -17,7 +17,7 @@ type SnakeCased<Type> = {
export default function snakeCaseTree<T>(object: T): SnakeCased<T> {
const snakeObject: any = {};
for (const key in object) {
snakeObject[snakeCase(key)] = snakeCaseValue(object[key]);
snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = snakeCaseValue(object[key]);
}
return snakeObject;
}