Merge remote-tracking branch 'origin/main' into tokio
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
3
assets/icons/share.svg
Normal 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 |
@@ -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",
|
||||
{
|
||||
|
||||
@@ -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
1435
assets/themes/cave-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1435
assets/themes/cave-light.json
Normal file
1435
assets/themes/cave-light.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
1435
assets/themes/solarized-dark.json
Normal file
1435
assets/themes/solarized-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1435
assets/themes/solarized-light.json
Normal file
1435
assets/themes/solarized-light.json
Normal file
File diff suppressed because it is too large
Load Diff
1435
assets/themes/sulphurpool-dark.json
Normal file
1435
assets/themes/sulphurpool-dark.json
Normal file
File diff suppressed because it is too large
Load Diff
1435
assets/themes/sulphurpool-light.json
Normal file
1435
assets/themes/sulphurpool-light.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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))
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -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
436
crates/vim/src/normal/change.rs
Normal file
436
crates/vim/src/normal/change.rs
Normal 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"},
|
||||
);
|
||||
}
|
||||
}
|
||||
386
crates/vim/src/normal/delete.rs
Normal file
386
crates/vim/src/normal/delete.rs
Normal 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"},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¶ms, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -56,7 +56,16 @@
|
||||
[
|
||||
"."
|
||||
";"
|
||||
] @delimiter
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
(string_literal)
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"?."
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
|
||||
@@ -11,4 +11,11 @@
|
||||
(true)
|
||||
(false)
|
||||
(null)
|
||||
] @constant
|
||||
] @constant
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
@@ -12,7 +12,7 @@
|
||||
(list_marker_star)
|
||||
(list_marker_dot)
|
||||
(list_marker_parenthesis)
|
||||
] @list_marker
|
||||
] @punctuation.list_marker
|
||||
|
||||
[
|
||||
(indented_code_block)
|
||||
|
||||
@@ -34,6 +34,20 @@
|
||||
((identifier) @constant
|
||||
(#match? @constant "^[A-Z][A-Z\\d_]+$"))
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
|
||||
(_
|
||||
.
|
||||
"<" @punctuation.bracket
|
||||
">" @punctuation.bracket)
|
||||
|
||||
[
|
||||
"as"
|
||||
"async"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"?."
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
567
styles/dist/solarized-dark.json
vendored
Normal 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
567
styles/dist/solarized-light.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,10 +50,6 @@ export default function selectorModal(theme: Theme): Object {
|
||||
top: 7,
|
||||
},
|
||||
},
|
||||
margin: {
|
||||
bottom: 52,
|
||||
top: 52,
|
||||
},
|
||||
shadow: shadow(theme),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
242
styles/src/themes/base16.ts
Normal 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
29
styles/src/themes/cave.ts
Normal 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);
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
29
styles/src/themes/solarized.ts
Normal file
29
styles/src/themes/solarized.ts
Normal 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);
|
||||
29
styles/src/themes/sulphurpool.ts
Normal file
29
styles/src/themes/sulphurpool.ts
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user