mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 02:48:34 +00:00
working jump to definition with tests
This commit is contained in:
parent
848445455d
commit
92ab107fc9
26 changed files with 956 additions and 564 deletions
|
@ -474,6 +474,14 @@ impl DisplaySnapshot {
|
||||||
pub fn longest_row(&self) -> u32 {
|
pub fn longest_row(&self) -> u32 {
|
||||||
self.blocks_snapshot.longest_row()
|
self.blocks_snapshot.longest_row()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn highlight_ranges<Tag: ?Sized + 'static>(
|
||||||
|
&self,
|
||||||
|
) -> Option<Arc<(HighlightStyle, Vec<Range<Anchor>>)>> {
|
||||||
|
let type_id = TypeId::of::<Tag>();
|
||||||
|
self.text_highlights.get(&Some(type_id)).cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub use multi_buffer::{
|
||||||
ToPoint,
|
ToPoint,
|
||||||
};
|
};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use project::{Project, ProjectPath, ProjectTransaction};
|
use project::{LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -4602,6 +4602,20 @@ impl Editor {
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
let definitions = definitions.await?;
|
let definitions = definitions.await?;
|
||||||
workspace.update(&mut cx, |workspace, cx| {
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn navigate_to_definitions(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
editor_handle: ViewHandle<Editor>,
|
||||||
|
definitions: Vec<LocationLink>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
let nav_history = workspace.active_pane().read(cx).nav_history().clone();
|
let nav_history = workspace.active_pane().read(cx).nav_history().clone();
|
||||||
for definition in definitions {
|
for definition in definitions {
|
||||||
let range = definition
|
let range = definition
|
||||||
|
@ -4609,8 +4623,7 @@ impl Editor {
|
||||||
.range
|
.range
|
||||||
.to_offset(definition.target.buffer.read(cx));
|
.to_offset(definition.target.buffer.read(cx));
|
||||||
|
|
||||||
let target_editor_handle =
|
let target_editor_handle = workspace.open_project_item(definition.target.buffer, cx);
|
||||||
workspace.open_project_item(definition.target.buffer, cx);
|
|
||||||
target_editor_handle.update(cx, |target_editor, cx| {
|
target_editor_handle.update(cx, |target_editor, cx| {
|
||||||
// When selecting a definition in a different buffer, disable the nav history
|
// When selecting a definition in a different buffer, disable the nav history
|
||||||
// to avoid creating a history entry at the previous cursor location.
|
// to avoid creating a history entry at the previous cursor location.
|
||||||
|
@ -4624,11 +4637,6 @@ impl Editor {
|
||||||
nav_history.borrow_mut().enable();
|
nav_history.borrow_mut().enable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Ok::<(), anyhow::Error>(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_all_references(
|
pub fn find_all_references(
|
||||||
|
|
|
@ -6,9 +6,9 @@ use super::{
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
||||||
hover_popover::HoverAt,
|
hover_popover::HoverAt,
|
||||||
|
link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink},
|
||||||
EditorStyle,
|
EditorStyle,
|
||||||
};
|
};
|
||||||
use crate::{hover_popover::HoverAt, link_go_to_definition::FetchDefinition};
|
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -105,7 +105,7 @@ impl EditorElement {
|
||||||
fn mouse_down(
|
fn mouse_down(
|
||||||
&self,
|
&self,
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
_: bool,
|
cmd: bool,
|
||||||
alt: bool,
|
alt: bool,
|
||||||
shift: bool,
|
shift: bool,
|
||||||
mut click_count: usize,
|
mut click_count: usize,
|
||||||
|
@ -113,6 +113,14 @@ impl EditorElement {
|
||||||
paint: &mut PaintState,
|
paint: &mut PaintState,
|
||||||
cx: &mut EventContext,
|
cx: &mut EventContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
if cmd && paint.text_bounds.contains_point(position) {
|
||||||
|
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
|
||||||
|
if overshoot.is_zero() {
|
||||||
|
cx.dispatch_action(GoToFetchedDefinition { point });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if paint.gutter_bounds.contains_point(position) {
|
if paint.gutter_bounds.contains_point(position) {
|
||||||
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
||||||
} else if !paint.text_bounds.contains_point(position) {
|
} else if !paint.text_bounds.contains_point(position) {
|
||||||
|
@ -204,6 +212,52 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mouse_moved(
|
||||||
|
&self,
|
||||||
|
position: Vector2F,
|
||||||
|
cmd: bool,
|
||||||
|
layout: &LayoutState,
|
||||||
|
paint: &PaintState,
|
||||||
|
cx: &mut EventContext,
|
||||||
|
) -> bool {
|
||||||
|
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
|
||||||
|
// Don't trigger hover popover if mouse is hovering over context menu
|
||||||
|
let point = if paint.text_bounds.contains_point(position) {
|
||||||
|
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
|
||||||
|
if overshoot.is_zero() {
|
||||||
|
Some(point)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.dispatch_action(UpdateGoToDefinitionLink {
|
||||||
|
point,
|
||||||
|
cmd_held: cmd,
|
||||||
|
});
|
||||||
|
|
||||||
|
if paint
|
||||||
|
.context_menu_bounds
|
||||||
|
.map_or(false, |context_menu_bounds| {
|
||||||
|
context_menu_bounds.contains_point(*position)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if paint
|
||||||
|
.hover_bounds
|
||||||
|
.map_or(false, |hover_bounds| hover_bounds.contains_point(position))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.dispatch_action(HoverAt { point });
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
|
fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
|
||||||
let view = self.view.upgrade(cx.app).unwrap();
|
let view = self.view.upgrade(cx.app).unwrap();
|
||||||
|
|
||||||
|
@ -219,6 +273,11 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
|
||||||
|
cx.dispatch_action(CmdChanged { cmd_down: cmd });
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn scroll(
|
fn scroll(
|
||||||
&self,
|
&self,
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
|
@ -1427,45 +1486,11 @@ impl Element for EditorElement {
|
||||||
precise,
|
precise,
|
||||||
} => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
} => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
||||||
Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
|
Event::KeyDown { input, .. } => self.key_down(input.as_deref(), cx),
|
||||||
|
Event::ModifiersChanged { cmd, .. } => self.modifiers_changed(*cmd, cx),
|
||||||
Event::MouseMoved { position, cmd, .. } => {
|
Event::MouseMoved { position, cmd, .. } => {
|
||||||
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
|
self.mouse_moved(*position, *cmd, layout, paint, cx)
|
||||||
// Don't trigger hover popover if mouse is hovering over context menu
|
|
||||||
|
|
||||||
let point = if paint.text_bounds.contains_point(*position) {
|
|
||||||
let (point, overshoot) =
|
|
||||||
paint.point_for_position(&self.snapshot(cx), layout, *position);
|
|
||||||
if overshoot.is_zero() {
|
|
||||||
Some(point)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if *cmd {
|
|
||||||
cx.dispatch_action(FetchDefinition { point });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if paint
|
|
||||||
.context_menu_bounds
|
|
||||||
.map_or(false, |context_menu_bounds| {
|
|
||||||
context_menu_bounds.contains_point(*position)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if paint
|
|
||||||
.hover_bounds
|
|
||||||
.map_or(false, |hover_bounds| hover_bounds.contains_point(*position))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.dispatch_action(HoverAt { point });
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::{ops::Range, time::Duration};
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
elements::{Flex, MouseEventHandler, Padding, Text},
|
elements::{Flex, MouseEventHandler, Padding, Text},
|
||||||
|
@ -9,6 +7,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::Bias;
|
use language::Bias;
|
||||||
use project::{HoverBlock, Project};
|
use project::{HoverBlock, Project};
|
||||||
|
use std::{ops::Range, time::Duration};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -1,60 +1,77 @@
|
||||||
use std::{
|
use std::ops::Range;
|
||||||
ops::Range,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{impl_internal_actions, MutableAppContext, Task, ViewContext};
|
||||||
actions,
|
use language::{Bias, ToOffset};
|
||||||
color::Color,
|
use project::LocationLink;
|
||||||
elements::{Flex, MouseEventHandler, Padding, Text},
|
use settings::Settings;
|
||||||
fonts::{HighlightStyle, Underline},
|
|
||||||
impl_internal_actions,
|
|
||||||
platform::CursorStyle,
|
|
||||||
Axis, Element, ElementBox, ModelHandle, MutableAppContext, RenderContext, Task, ViewContext,
|
|
||||||
};
|
|
||||||
use language::Bias;
|
|
||||||
use project::{HoverBlock, Project};
|
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{
|
use crate::{Anchor, DisplayPoint, Editor, GoToDefinition};
|
||||||
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
|
|
||||||
EditorStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct FetchDefinition {
|
pub struct UpdateGoToDefinitionLink {
|
||||||
pub point: Option<DisplayPoint>,
|
pub point: Option<DisplayPoint>,
|
||||||
|
pub cmd_held: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct CmdChanged {
|
||||||
|
pub cmd_down: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct GoToFetchedDefinition {
|
pub struct GoToFetchedDefinition {
|
||||||
pub point: Option<DisplayPoint>,
|
pub point: DisplayPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_internal_actions!(edtior, [FetchDefinition, GoToFetchedDefinition]);
|
impl_internal_actions!(
|
||||||
|
editor,
|
||||||
|
[UpdateGoToDefinitionLink, CmdChanged, GoToFetchedDefinition]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(fetch_definition);
|
cx.add_action(update_go_to_definition_link);
|
||||||
|
cx.add_action(cmd_changed);
|
||||||
cx.add_action(go_to_fetched_definition);
|
cx.add_action(go_to_fetched_definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct LinkGoToDefinitionState {
|
pub struct LinkGoToDefinitionState {
|
||||||
|
pub last_mouse_point: Option<DisplayPoint>,
|
||||||
pub triggered_from: Option<Anchor>,
|
pub triggered_from: Option<Anchor>,
|
||||||
pub symbol_range: Option<Range<Anchor>>,
|
pub symbol_range: Option<Range<Anchor>>,
|
||||||
|
pub definitions: Vec<LocationLink>,
|
||||||
pub task: Option<Task<Option<()>>>,
|
pub task: Option<Task<Option<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_definition(
|
pub fn update_go_to_definition_link(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
&FetchDefinition { point }: &FetchDefinition,
|
&UpdateGoToDefinitionLink { point, cmd_held }: &UpdateGoToDefinitionLink,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) {
|
) {
|
||||||
|
editor.link_go_to_definition_state.last_mouse_point = point;
|
||||||
|
if cmd_held {
|
||||||
if let Some(point) = point {
|
if let Some(point) = point {
|
||||||
show_link_definition(editor, point, cx);
|
show_link_definition(editor, point, cx);
|
||||||
} else {
|
return;
|
||||||
//TODO: Also needs to be dispatched when cmd modifier is released
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hide_link_definition(editor, cx);
|
hide_link_definition(editor, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd_changed(
|
||||||
|
editor: &mut Editor,
|
||||||
|
&CmdChanged { cmd_down }: &CmdChanged,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
if let Some(point) = editor.link_go_to_definition_state.last_mouse_point {
|
||||||
|
if cmd_down {
|
||||||
|
show_link_definition(editor, point, cx);
|
||||||
|
} else {
|
||||||
|
hide_link_definition(editor, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +118,22 @@ pub fn show_link_definition(
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.anchor_at(multibuffer_offset, Bias::Left);
|
.anchor_at(multibuffer_offset, Bias::Left);
|
||||||
|
|
||||||
// Don't request again if the location is the same as the previous request
|
// Don't request again if the location is within the symbol region of a previous request
|
||||||
|
if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
|
||||||
|
if symbol_range
|
||||||
|
.start
|
||||||
|
.cmp(&anchor, &snapshot.buffer_snapshot)
|
||||||
|
.is_le()
|
||||||
|
&& symbol_range
|
||||||
|
.end
|
||||||
|
.cmp(&anchor, &snapshot.buffer_snapshot)
|
||||||
|
.is_ge()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't request from the exact same location again
|
||||||
if let Some(triggered_from) = &editor.link_go_to_definition_state.triggered_from {
|
if let Some(triggered_from) = &editor.link_go_to_definition_state.triggered_from {
|
||||||
if triggered_from
|
if triggered_from
|
||||||
.cmp(&anchor, &snapshot.buffer_snapshot)
|
.cmp(&anchor, &snapshot.buffer_snapshot)
|
||||||
|
@ -120,11 +152,10 @@ pub fn show_link_definition(
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let origin_range = definition_request.await.ok().and_then(|definition_result| {
|
let result = definition_request.await.ok().map(|definition_result| {
|
||||||
definition_result
|
(
|
||||||
.into_iter()
|
definition_result.iter().find_map(|link| {
|
||||||
.filter_map(|link| {
|
link.origin.as_ref().map(|origin| {
|
||||||
link.origin.map(|origin| {
|
|
||||||
let start = snapshot
|
let start = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
|
.anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
|
||||||
|
@ -134,25 +165,65 @@ pub fn show_link_definition(
|
||||||
|
|
||||||
start..end
|
start..end
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
.next()
|
definition_result,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let Some(origin_range) = origin_range {
|
// Clear any existing highlights
|
||||||
|
this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
||||||
|
this.link_go_to_definition_state.triggered_from = Some(anchor.clone());
|
||||||
|
this.link_go_to_definition_state.symbol_range = result
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|(symbol_range, _)| symbol_range.clone());
|
||||||
|
|
||||||
|
if let Some((symbol_range, definitions)) = result {
|
||||||
|
this.link_go_to_definition_state.definitions = definitions.clone();
|
||||||
|
|
||||||
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
// Only show highlight if there exists a definition to jump to that doesn't contain
|
||||||
|
// the current location.
|
||||||
|
if definitions.iter().any(|definition| {
|
||||||
|
let target = &definition.target;
|
||||||
|
if target.buffer == buffer {
|
||||||
|
let range = &target.range;
|
||||||
|
// Expand range by one character as lsp definition ranges include positions adjacent
|
||||||
|
// but not contained by the symbol range
|
||||||
|
let start = buffer_snapshot.clip_offset(
|
||||||
|
range.start.to_offset(&buffer_snapshot).saturating_sub(1),
|
||||||
|
Bias::Left,
|
||||||
|
);
|
||||||
|
let end = buffer_snapshot.clip_offset(
|
||||||
|
range.end.to_offset(&buffer_snapshot) + 1,
|
||||||
|
Bias::Right,
|
||||||
|
);
|
||||||
|
let offset = buffer_position.to_offset(&buffer_snapshot);
|
||||||
|
!(start <= offset && end >= offset)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
// If no symbol range returned from language server, use the surrounding word.
|
||||||
|
let highlight_range = symbol_range.unwrap_or_else(|| {
|
||||||
|
let snapshot = &snapshot.buffer_snapshot;
|
||||||
|
let (offset_range, _) = snapshot.surrounding_word(anchor);
|
||||||
|
|
||||||
|
snapshot.anchor_before(offset_range.start)
|
||||||
|
..snapshot.anchor_after(offset_range.end)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight symbol using theme link definition highlight style
|
||||||
|
let style = cx.global::<Settings>().theme.editor.link_definition;
|
||||||
this.highlight_text::<LinkGoToDefinitionState>(
|
this.highlight_text::<LinkGoToDefinitionState>(
|
||||||
vec![origin_range],
|
vec![highlight_range],
|
||||||
HighlightStyle {
|
style,
|
||||||
underline: Some(Underline {
|
|
||||||
color: Some(Color::red()),
|
|
||||||
thickness: 1.0.into(),
|
|
||||||
squiggly: false,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
hide_link_definition(this, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -166,21 +237,363 @@ pub fn show_link_definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
// only notify the context once
|
|
||||||
if editor.link_go_to_definition_state.symbol_range.is_some() {
|
if editor.link_go_to_definition_state.symbol_range.is_some() {
|
||||||
editor.link_go_to_definition_state.symbol_range.take();
|
editor.link_go_to_definition_state.symbol_range.take();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.link_go_to_definition_state.task = None;
|
|
||||||
editor.link_go_to_definition_state.triggered_from = None;
|
editor.link_go_to_definition_state.triggered_from = None;
|
||||||
|
editor.link_go_to_definition_state.task = None;
|
||||||
|
|
||||||
editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_to_fetched_definition(
|
pub fn go_to_fetched_definition(
|
||||||
editor: &mut Editor,
|
workspace: &mut Workspace,
|
||||||
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
|
let active_item = workspace.active_item(cx);
|
||||||
|
let editor_handle = if let Some(editor) = active_item
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|item| item.act_as::<Editor>(cx))
|
||||||
|
{
|
||||||
|
editor
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut definitions = Vec::new();
|
||||||
|
|
||||||
|
editor_handle.update(cx, |editor, cx| {
|
||||||
|
hide_link_definition(editor, cx);
|
||||||
|
std::mem::swap(
|
||||||
|
&mut editor.link_go_to_definition_state.definitions,
|
||||||
|
&mut definitions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if !definitions.is_empty() {
|
||||||
|
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||||
|
} else {
|
||||||
|
editor_handle.update(cx, |editor, cx| {
|
||||||
|
editor.change_selections(None, cx, |s| s.select_display_ranges(vec![*point..*point]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Editor::go_to_definition(workspace, &GoToDefinition, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use futures::StreamExt;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
use crate::test::EditorLspTestContext;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_state(indoc! {"
|
||||||
|
fn |test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Basic hold cmd, expect highlight in region if response contains definition
|
||||||
|
let hover_point = cx.display_point(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_w|ork();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
let symbol_range = cx.lsp_range(indoc! {"
|
||||||
|
fn test()
|
||||||
|
[do_work]();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
let target_range = cx.lsp_range(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn [do_work]()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
let mut requests =
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
|
lsp::LocationLink {
|
||||||
|
origin_selection_range: Some(symbol_range),
|
||||||
|
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||||
|
target_range,
|
||||||
|
target_selection_range: target_range,
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
update_go_to_definition_link(
|
||||||
|
editor,
|
||||||
|
&UpdateGoToDefinitionLink {
|
||||||
|
point: Some(hover_point),
|
||||||
|
cmd_held: true,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
requests.next().await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
[do_work]();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Unpress cmd causes highlight to go away
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
cmd_changed(editor, &CmdChanged { cmd_down: false }, cx);
|
||||||
|
});
|
||||||
|
// Assert no link highlights
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Response without source range still highlights word
|
||||||
|
let mut requests =
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
|
lsp::LocationLink {
|
||||||
|
// No origin range
|
||||||
|
origin_selection_range: None,
|
||||||
|
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||||
|
target_range,
|
||||||
|
target_selection_range: target_range,
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
update_go_to_definition_link(
|
||||||
|
editor,
|
||||||
|
&UpdateGoToDefinitionLink {
|
||||||
|
point: Some(hover_point),
|
||||||
|
cmd_held: true,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
requests.next().await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
[do_work]();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Moving mouse to location with no response dismisses highlight
|
||||||
|
let hover_point = cx.display_point(indoc! {"
|
||||||
|
f|n test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
let mut requests =
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
// No definitions returned
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||||
|
});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
update_go_to_definition_link(
|
||||||
|
editor,
|
||||||
|
&UpdateGoToDefinitionLink {
|
||||||
|
point: Some(hover_point),
|
||||||
|
cmd_held: true,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
requests.next().await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Assert no link highlights
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Move mouse without cmd and then pressing cmd triggers highlight
|
||||||
|
let hover_point = cx.display_point(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
te|st();"});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
update_go_to_definition_link(
|
||||||
|
editor,
|
||||||
|
&UpdateGoToDefinitionLink {
|
||||||
|
point: Some(hover_point),
|
||||||
|
cmd_held: false,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Assert no link highlights
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
let symbol_range = cx.lsp_range(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
[test]();"});
|
||||||
|
let target_range = cx.lsp_range(indoc! {"
|
||||||
|
fn [test]()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
let mut requests =
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
|
lsp::LocationLink {
|
||||||
|
origin_selection_range: Some(symbol_range),
|
||||||
|
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||||
|
target_range,
|
||||||
|
target_selection_range: target_range,
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
cmd_changed(editor, &CmdChanged { cmd_down: true }, cx);
|
||||||
|
});
|
||||||
|
requests.next().await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
[test]();"});
|
||||||
|
|
||||||
|
// Moving within symbol range doesn't re-request
|
||||||
|
let hover_point = cx.display_point(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
tes|t();"});
|
||||||
|
cx.update_editor(|editor, cx| {
|
||||||
|
update_go_to_definition_link(
|
||||||
|
editor,
|
||||||
|
&UpdateGoToDefinitionLink {
|
||||||
|
point: Some(hover_point),
|
||||||
|
cmd_held: true,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
[test]();"});
|
||||||
|
|
||||||
|
// Cmd click with existing definition doesn't re-request and dismisses highlight
|
||||||
|
cx.update_workspace(|workspace, cx| {
|
||||||
|
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
|
||||||
|
});
|
||||||
|
// Assert selection moved to to definition
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
// Empty definition response to make sure we aren't hitting the lsp and using
|
||||||
|
// the cached location instead
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||||
|
});
|
||||||
|
cx.assert_editor_state(indoc! {"
|
||||||
|
fn [test}()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
// Assert no link highlights after jump
|
||||||
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
// Cmd click without existing definition requests and jumps
|
||||||
|
let hover_point = cx.display_point(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_w|ork();
|
||||||
|
|
||||||
|
fn do_work()
|
||||||
|
test();"});
|
||||||
|
let target_range = cx.lsp_range(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn [do_work]()
|
||||||
|
test();"});
|
||||||
|
|
||||||
|
let mut requests =
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||||
|
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||||
|
lsp::LocationLink {
|
||||||
|
origin_selection_range: None,
|
||||||
|
target_uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||||
|
target_range,
|
||||||
|
target_selection_range: target_range,
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
cx.update_workspace(|workspace, cx| {
|
||||||
|
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
|
||||||
|
});
|
||||||
|
requests.next().await;
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
cx.assert_editor_state(indoc! {"
|
||||||
|
fn test()
|
||||||
|
do_work();
|
||||||
|
|
||||||
|
fn [do_work}()
|
||||||
|
test();"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -536,7 +536,6 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||||
self.select_anchors(selections)
|
self.select_anchors(selections)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn select_display_ranges<T>(&mut self, ranges: T)
|
pub fn select_display_ranges<T>(&mut self, ranges: T)
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = Range<DisplayPoint>>,
|
T: IntoIterator<Item = Range<DisplayPoint>>,
|
||||||
|
|
|
@ -7,19 +7,20 @@ use futures::StreamExt;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use gpui::{keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
|
use gpui::{json, keymap::Keystroke, AppContext, ModelHandle, ViewContext, ViewHandle};
|
||||||
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection};
|
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, Selection};
|
||||||
use project::{FakeFs, Project};
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use util::{
|
use util::{
|
||||||
set_eq,
|
assert_set_eq, set_eq,
|
||||||
test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
|
test::{marked_text, marked_text_ranges, marked_text_ranges_by, SetEqError},
|
||||||
};
|
};
|
||||||
|
use workspace::{pane, AppState, Workspace, WorkspaceHandle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||||
multi_buffer::ToPointUtf16,
|
multi_buffer::ToPointUtf16,
|
||||||
Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
|
AnchorRangeExt, Autoscroll, DisplayPoint, Editor, EditorMode, MultiBuffer, ToPoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -215,6 +216,24 @@ impl<'a> EditorTestContext<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
|
||||||
|
let (unmarked, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']').into()]);
|
||||||
|
assert_eq!(unmarked, self.buffer_text());
|
||||||
|
|
||||||
|
let asserted_ranges = ranges.remove(&('[', ']').into()).unwrap();
|
||||||
|
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||||
|
let actual_ranges: Vec<Range<usize>> = snapshot
|
||||||
|
.display_snapshot
|
||||||
|
.highlight_ranges::<Tag>()
|
||||||
|
.map(|ranges| ranges.as_ref().clone().1)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_set_eq!(asserted_ranges, actual_ranges);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
|
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Selection<usize>>) {
|
||||||
let mut empty_selections = Vec::new();
|
let mut empty_selections = Vec::new();
|
||||||
let mut reverse_selections = Vec::new();
|
let mut reverse_selections = Vec::new();
|
||||||
|
@ -390,6 +409,7 @@ impl<'a> DerefMut for EditorTestContext<'a> {
|
||||||
pub struct EditorLspTestContext<'a> {
|
pub struct EditorLspTestContext<'a> {
|
||||||
pub cx: EditorTestContext<'a>,
|
pub cx: EditorTestContext<'a>,
|
||||||
pub lsp: lsp::FakeLanguageServer,
|
pub lsp: lsp::FakeLanguageServer,
|
||||||
|
pub workspace: ViewHandle<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EditorLspTestContext<'a> {
|
impl<'a> EditorLspTestContext<'a> {
|
||||||
|
@ -398,8 +418,17 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
capabilities: lsp::ServerCapabilities,
|
capabilities: lsp::ServerCapabilities,
|
||||||
cx: &'a mut gpui::TestAppContext,
|
cx: &'a mut gpui::TestAppContext,
|
||||||
) -> EditorLspTestContext<'a> {
|
) -> EditorLspTestContext<'a> {
|
||||||
|
use json::json;
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
crate::init(cx);
|
||||||
|
pane::init(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let params = cx.update(AppState::test);
|
||||||
|
|
||||||
let file_name = format!(
|
let file_name = format!(
|
||||||
"/file.{}",
|
"file.{}",
|
||||||
language
|
language
|
||||||
.path_suffixes()
|
.path_suffixes()
|
||||||
.first()
|
.first()
|
||||||
|
@ -411,31 +440,37 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background().clone());
|
let project = Project::test(params.fs.clone(), [], cx).await;
|
||||||
fs.insert_file(file_name.clone(), "".to_string()).await;
|
|
||||||
|
|
||||||
let project = Project::test(fs, [file_name.as_ref()], cx).await;
|
|
||||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||||
let buffer = project
|
|
||||||
.update(cx, |project, cx| project.open_local_buffer(file_name, cx))
|
params
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree("/root", json!({ "dir": { file_name: "" }}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
|
project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.find_or_create_local_worktree("/root", true, cx)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||||
|
.await;
|
||||||
|
|
||||||
let (window_id, editor) = cx.update(|cx| {
|
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||||
cx.set_global(Settings::test(cx));
|
let item = workspace
|
||||||
crate::init(cx);
|
.update(cx, |workspace, cx| workspace.open_path(file, true, cx))
|
||||||
|
.await
|
||||||
|
.expect("Could not open test file");
|
||||||
|
|
||||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
let editor = cx.update(|cx| {
|
||||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
item.act_as::<Editor>(cx)
|
||||||
|
.expect("Opened test file wasn't an editor")
|
||||||
Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update(cx, |_, cx| cx.focus_self());
|
editor.update(cx, |_, cx| cx.focus_self());
|
||||||
|
|
||||||
(window_id, editor)
|
|
||||||
});
|
|
||||||
|
|
||||||
let lsp = fake_servers.next().await.unwrap();
|
let lsp = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -445,6 +480,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
editor,
|
editor,
|
||||||
},
|
},
|
||||||
lsp,
|
lsp,
|
||||||
|
workspace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,6 +529,13 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
lsp::Range { start, end }
|
lsp::Range { start, end }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_workspace<F, T>(&mut self, update: F) -> T
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
||||||
|
{
|
||||||
|
self.workspace.update(self.cx.cx, update)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for EditorLspTestContext<'a> {
|
impl<'a> Deref for EditorLspTestContext<'a> {
|
||||||
|
|
|
@ -13,6 +13,16 @@ pub enum Event {
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
is_held: bool,
|
is_held: bool,
|
||||||
},
|
},
|
||||||
|
KeyUp {
|
||||||
|
keystroke: Keystroke,
|
||||||
|
input: Option<String>,
|
||||||
|
},
|
||||||
|
ModifiersChanged {
|
||||||
|
ctrl: bool,
|
||||||
|
alt: bool,
|
||||||
|
shift: bool,
|
||||||
|
cmd: bool,
|
||||||
|
},
|
||||||
ScrollWheel {
|
ScrollWheel {
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
delta: Vector2F,
|
delta: Vector2F,
|
||||||
|
@ -76,6 +86,8 @@ impl Event {
|
||||||
pub fn position(&self) -> Option<Vector2F> {
|
pub fn position(&self) -> Option<Vector2F> {
|
||||||
match self {
|
match self {
|
||||||
Event::KeyDown { .. } => None,
|
Event::KeyDown { .. } => None,
|
||||||
|
Event::KeyUp { .. } => None,
|
||||||
|
Event::ModifiersChanged { .. } => None,
|
||||||
Event::ScrollWheel { position, .. }
|
Event::ScrollWheel { position, .. }
|
||||||
| Event::LeftMouseDown { position, .. }
|
| Event::LeftMouseDown { position, .. }
|
||||||
| Event::LeftMouseUp { position, .. }
|
| Event::LeftMouseUp { position, .. }
|
||||||
|
|
|
@ -52,6 +52,20 @@ impl Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
|
NSEventType::NSFlagsChanged => {
|
||||||
|
let modifiers = native_event.modifierFlags();
|
||||||
|
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||||
|
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||||
|
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||||
|
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||||
|
|
||||||
|
Some(Self::ModifiersChanged {
|
||||||
|
ctrl,
|
||||||
|
alt,
|
||||||
|
shift,
|
||||||
|
cmd,
|
||||||
|
})
|
||||||
|
}
|
||||||
NSEventType::NSKeyDown => {
|
NSEventType::NSKeyDown => {
|
||||||
let modifiers = native_event.modifierFlags();
|
let modifiers = native_event.modifierFlags();
|
||||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||||
|
@ -60,71 +74,7 @@ impl Event {
|
||||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||||
|
|
||||||
let unmodified_chars = CStr::from_ptr(
|
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
||||||
native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char,
|
|
||||||
)
|
|
||||||
.to_str()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut input = None;
|
|
||||||
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
|
|
||||||
use cocoa::appkit::*;
|
|
||||||
const BACKSPACE_KEY: u16 = 0x7f;
|
|
||||||
const ENTER_KEY: u16 = 0x0d;
|
|
||||||
const ESCAPE_KEY: u16 = 0x1b;
|
|
||||||
const TAB_KEY: u16 = 0x09;
|
|
||||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
|
||||||
const SPACE_KEY: u16 = b' ' as u16;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match first_char as u16 {
|
|
||||||
SPACE_KEY => {
|
|
||||||
input = Some(" ".to_string());
|
|
||||||
"space"
|
|
||||||
}
|
|
||||||
BACKSPACE_KEY => "backspace",
|
|
||||||
ENTER_KEY => "enter",
|
|
||||||
ESCAPE_KEY => "escape",
|
|
||||||
TAB_KEY => "tab",
|
|
||||||
SHIFT_TAB_KEY => "tab",
|
|
||||||
|
|
||||||
NSUpArrowFunctionKey => "up",
|
|
||||||
NSDownArrowFunctionKey => "down",
|
|
||||||
NSLeftArrowFunctionKey => "left",
|
|
||||||
NSRightArrowFunctionKey => "right",
|
|
||||||
NSPageUpFunctionKey => "pageup",
|
|
||||||
NSPageDownFunctionKey => "pagedown",
|
|
||||||
NSDeleteFunctionKey => "delete",
|
|
||||||
NSF1FunctionKey => "f1",
|
|
||||||
NSF2FunctionKey => "f2",
|
|
||||||
NSF3FunctionKey => "f3",
|
|
||||||
NSF4FunctionKey => "f4",
|
|
||||||
NSF5FunctionKey => "f5",
|
|
||||||
NSF6FunctionKey => "f6",
|
|
||||||
NSF7FunctionKey => "f7",
|
|
||||||
NSF8FunctionKey => "f8",
|
|
||||||
NSF9FunctionKey => "f9",
|
|
||||||
NSF10FunctionKey => "f10",
|
|
||||||
NSF11FunctionKey => "f11",
|
|
||||||
NSF12FunctionKey => "f12",
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
if !cmd && !ctrl && !function {
|
|
||||||
input = Some(
|
|
||||||
CStr::from_ptr(
|
|
||||||
native_event.characters().UTF8String() as *mut c_char
|
|
||||||
)
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
unmodified_chars
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Self::KeyDown {
|
Some(Self::KeyDown {
|
||||||
keystroke: Keystroke {
|
keystroke: Keystroke {
|
||||||
|
@ -138,6 +88,27 @@ impl Event {
|
||||||
is_held: native_event.isARepeat() == YES,
|
is_held: native_event.isARepeat() == YES,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
NSEventType::NSKeyUp => {
|
||||||
|
let modifiers = native_event.modifierFlags();
|
||||||
|
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||||
|
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||||
|
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||||
|
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||||
|
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||||
|
|
||||||
|
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
||||||
|
|
||||||
|
Some(Self::KeyUp {
|
||||||
|
keystroke: Keystroke {
|
||||||
|
ctrl,
|
||||||
|
alt,
|
||||||
|
shift,
|
||||||
|
cmd,
|
||||||
|
key: unmodified_chars.into(),
|
||||||
|
},
|
||||||
|
input,
|
||||||
|
})
|
||||||
|
}
|
||||||
NSEventType::NSLeftMouseDown => {
|
NSEventType::NSLeftMouseDown => {
|
||||||
let modifiers = native_event.modifierFlags();
|
let modifiers = native_event.modifierFlags();
|
||||||
window_height.map(|window_height| Self::LeftMouseDown {
|
window_height.map(|window_height| Self::LeftMouseDown {
|
||||||
|
@ -260,3 +231,72 @@ impl Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn get_key_text(
|
||||||
|
native_event: id,
|
||||||
|
cmd: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
function: bool,
|
||||||
|
) -> Option<(&'static str, Option<String>)> {
|
||||||
|
let unmodified_chars =
|
||||||
|
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut input = None;
|
||||||
|
let first_char = unmodified_chars.chars().next()?;
|
||||||
|
use cocoa::appkit::*;
|
||||||
|
const BACKSPACE_KEY: u16 = 0x7f;
|
||||||
|
const ENTER_KEY: u16 = 0x0d;
|
||||||
|
const ESCAPE_KEY: u16 = 0x1b;
|
||||||
|
const TAB_KEY: u16 = 0x09;
|
||||||
|
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||||
|
const SPACE_KEY: u16 = b' ' as u16;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
let unmodified_chars = match first_char as u16 {
|
||||||
|
SPACE_KEY => {
|
||||||
|
input = Some(" ".to_string());
|
||||||
|
"space"
|
||||||
|
}
|
||||||
|
BACKSPACE_KEY => "backspace",
|
||||||
|
ENTER_KEY => "enter",
|
||||||
|
ESCAPE_KEY => "escape",
|
||||||
|
TAB_KEY => "tab",
|
||||||
|
SHIFT_TAB_KEY => "tab",
|
||||||
|
|
||||||
|
NSUpArrowFunctionKey => "up",
|
||||||
|
NSDownArrowFunctionKey => "down",
|
||||||
|
NSLeftArrowFunctionKey => "left",
|
||||||
|
NSRightArrowFunctionKey => "right",
|
||||||
|
NSPageUpFunctionKey => "pageup",
|
||||||
|
NSPageDownFunctionKey => "pagedown",
|
||||||
|
NSDeleteFunctionKey => "delete",
|
||||||
|
NSF1FunctionKey => "f1",
|
||||||
|
NSF2FunctionKey => "f2",
|
||||||
|
NSF3FunctionKey => "f3",
|
||||||
|
NSF4FunctionKey => "f4",
|
||||||
|
NSF5FunctionKey => "f5",
|
||||||
|
NSF6FunctionKey => "f6",
|
||||||
|
NSF7FunctionKey => "f7",
|
||||||
|
NSF8FunctionKey => "f8",
|
||||||
|
NSF9FunctionKey => "f9",
|
||||||
|
NSF10FunctionKey => "f10",
|
||||||
|
NSF11FunctionKey => "f11",
|
||||||
|
NSF12FunctionKey => "f12",
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if !cmd && !ctrl && !function {
|
||||||
|
input = Some(
|
||||||
|
CStr::from_ptr(native_event.characters().UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
unmodified_chars
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((unmodified_chars, input))
|
||||||
|
}
|
||||||
|
|
|
@ -135,6 +135,10 @@ unsafe fn build_classes() {
|
||||||
sel!(scrollWheel:),
|
sel!(scrollWheel:),
|
||||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(flagsChanged:),
|
||||||
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(cancelOperation:),
|
sel!(cancelOperation:),
|
||||||
cancel_operation as extern "C" fn(&Object, Sel, id),
|
cancel_operation as extern "C" fn(&Object, Sel, id),
|
||||||
|
@ -181,6 +185,7 @@ struct WindowState {
|
||||||
last_fresh_keydown: Option<(Keystroke, Option<String>)>,
|
last_fresh_keydown: Option<(Keystroke, Option<String>)>,
|
||||||
layer: id,
|
layer: id,
|
||||||
traffic_light_position: Option<Vector2F>,
|
traffic_light_position: Option<Vector2F>,
|
||||||
|
previous_modifiers_changed_event: Option<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -263,6 +268,7 @@ impl Window {
|
||||||
last_fresh_keydown: None,
|
last_fresh_keydown: None,
|
||||||
layer,
|
layer,
|
||||||
traffic_light_position: options.traffic_light_position,
|
traffic_light_position: options.traffic_light_position,
|
||||||
|
previous_modifiers_changed_event: None,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
(*native_window).set_ivar(
|
(*native_window).set_ivar(
|
||||||
|
@ -611,6 +617,31 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||||
Event::LeftMouseUp { .. } => {
|
Event::LeftMouseUp { .. } => {
|
||||||
window_state_borrow.synthetic_drag_counter += 1;
|
window_state_borrow.synthetic_drag_counter += 1;
|
||||||
}
|
}
|
||||||
|
Event::ModifiersChanged {
|
||||||
|
ctrl,
|
||||||
|
alt,
|
||||||
|
shift,
|
||||||
|
cmd,
|
||||||
|
} => {
|
||||||
|
// Only raise modifiers changed event when they have actually changed
|
||||||
|
if let Some(Event::ModifiersChanged {
|
||||||
|
ctrl: prev_ctrl,
|
||||||
|
alt: prev_alt,
|
||||||
|
shift: prev_shift,
|
||||||
|
cmd: prev_cmd,
|
||||||
|
}) = &window_state_borrow.previous_modifiers_changed_event
|
||||||
|
{
|
||||||
|
if prev_ctrl == ctrl
|
||||||
|
&& prev_alt == alt
|
||||||
|
&& prev_shift == shift
|
||||||
|
&& prev_cmd == cmd
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,13 +202,13 @@ pub struct DiagnosticSummary {
|
||||||
pub warning_count: usize,
|
pub warning_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Location {
|
pub struct Location {
|
||||||
pub buffer: ModelHandle<Buffer>,
|
pub buffer: ModelHandle<Buffer>,
|
||||||
pub range: Range<language::Anchor>,
|
pub range: Range<language::Anchor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocationLink {
|
pub struct LocationLink {
|
||||||
pub origin: Option<Location>,
|
pub origin: Option<Location>,
|
||||||
pub target: Location,
|
pub target: Location,
|
||||||
|
|
|
@ -446,6 +446,7 @@ pub struct Editor {
|
||||||
pub code_actions_indicator: Color,
|
pub code_actions_indicator: Color,
|
||||||
pub unnecessary_code_fade: f32,
|
pub unnecessary_code_fade: f32,
|
||||||
pub hover_popover: HoverPopover,
|
pub hover_popover: HoverPopover,
|
||||||
|
pub link_definition: HighlightStyle,
|
||||||
pub jump_icon: Interactive<IconButton>,
|
pub jump_icon: Interactive<IconButton>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build-themes && npm run build-tokens",
|
"build": "npm run build-themes && npm run build-tokens",
|
||||||
"build-themes": "ts-node ./src/buildThemes.ts",
|
"build-themes": "ts-node ./src/buildThemes.ts"
|
||||||
"build-tokens": "ts-node ./src/buildTokens.ts"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import * as fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import themes from "./themes";
|
|
||||||
import Theme from "./themes/common/theme";
|
|
||||||
import { colors, fontFamilies, fontSizes, fontWeights, sizes } from "./tokens";
|
|
||||||
|
|
||||||
// Organize theme tokens
|
|
||||||
function themeTokens(theme: Theme) {
|
|
||||||
return {
|
|
||||||
meta: {
|
|
||||||
themeName: theme.name,
|
|
||||||
},
|
|
||||||
text: theme.textColor,
|
|
||||||
icon: theme.iconColor,
|
|
||||||
background: theme.backgroundColor,
|
|
||||||
border: theme.borderColor,
|
|
||||||
editor: theme.editor,
|
|
||||||
syntax: {
|
|
||||||
primary: theme.syntax.primary.color,
|
|
||||||
comment: theme.syntax.comment.color,
|
|
||||||
keyword: theme.syntax.keyword.color,
|
|
||||||
function: theme.syntax.function.color,
|
|
||||||
type: theme.syntax.type.color,
|
|
||||||
variant: theme.syntax.variant.color,
|
|
||||||
property: theme.syntax.property.color,
|
|
||||||
enum: theme.syntax.enum.color,
|
|
||||||
operator: theme.syntax.operator.color,
|
|
||||||
string: theme.syntax.string.color,
|
|
||||||
number: theme.syntax.number.color,
|
|
||||||
boolean: theme.syntax.boolean.color,
|
|
||||||
},
|
|
||||||
player: theme.player,
|
|
||||||
shadow: theme.shadow,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Organize core tokens
|
|
||||||
const coreTokens = {
|
|
||||||
color: colors,
|
|
||||||
text: {
|
|
||||||
family: fontFamilies,
|
|
||||||
weight: fontWeights,
|
|
||||||
},
|
|
||||||
size: sizes,
|
|
||||||
fontSize: fontSizes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const combinedTokens: any = {};
|
|
||||||
|
|
||||||
const distPath = path.resolve(`${__dirname}/../dist`);
|
|
||||||
for (const file of fs.readdirSync(distPath)) {
|
|
||||||
fs.unlinkSync(path.join(distPath, file));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add core tokens to the combined tokens and write `core.json`.
|
|
||||||
// We write `core.json` as a separate file for the design team's convenience, but it isn't consumed by Figma Tokens directly.
|
|
||||||
const corePath = path.join(distPath, "core.json");
|
|
||||||
fs.writeFileSync(corePath, JSON.stringify(coreTokens, null, 2));
|
|
||||||
console.log(`- ${corePath} created`);
|
|
||||||
combinedTokens.core = coreTokens;
|
|
||||||
|
|
||||||
// Add each theme to the combined tokens and write ${theme}.json.
|
|
||||||
// We write `${theme}.json` as a separate file for the design team's convenience, but it isn't consumed by Figma Tokens directly.
|
|
||||||
themes.forEach((theme) => {
|
|
||||||
const themePath = `${distPath}/${theme.name}.json`
|
|
||||||
fs.writeFileSync(themePath, JSON.stringify(themeTokens(theme), null, 2));
|
|
||||||
console.log(`- ${themePath} created`);
|
|
||||||
combinedTokens[theme.name] = themeTokens(theme);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Write combined tokens to `tokens.json`. This file is consumed by the Figma Tokens plugin to keep our designs consistent with the app.
|
|
||||||
const combinedPath = path.resolve(`${distPath}/tokens.json`);
|
|
||||||
fs.writeFileSync(combinedPath, JSON.stringify(combinedTokens, null, 2));
|
|
||||||
console.log(`- ${combinedPath} created`);
|
|
65
styles/src/common.ts
Normal file
65
styles/src/common.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
export const fontFamilies = {
|
||||||
|
sans: "Zed Sans",
|
||||||
|
mono: "Zed Mono",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fontSizes = {
|
||||||
|
"3xs": 8,
|
||||||
|
"2xs": 10,
|
||||||
|
xs: 12,
|
||||||
|
sm: 14,
|
||||||
|
md: 16,
|
||||||
|
lg: 18,
|
||||||
|
xl: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FontWeight = "thin"
|
||||||
|
| "extra_light"
|
||||||
|
| "light"
|
||||||
|
| "normal"
|
||||||
|
| "medium"
|
||||||
|
| "semibold"
|
||||||
|
| "bold"
|
||||||
|
| "extra_bold"
|
||||||
|
| "black";
|
||||||
|
export const fontWeights: { [key: string]: FontWeight } = {
|
||||||
|
thin: "thin",
|
||||||
|
extra_light: "extra_light",
|
||||||
|
light: "light",
|
||||||
|
normal: "normal",
|
||||||
|
medium: "medium",
|
||||||
|
semibold: "semibold",
|
||||||
|
bold: "bold",
|
||||||
|
extra_bold: "extra_bold",
|
||||||
|
black: "black"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sizes = {
|
||||||
|
px: 1,
|
||||||
|
xs: 2,
|
||||||
|
sm: 4,
|
||||||
|
md: 6,
|
||||||
|
lg: 8,
|
||||||
|
xl: 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const colors = {
|
||||||
|
// neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1
|
||||||
|
// rose: colorRamp("#F43F5EFF"),
|
||||||
|
// red: colorRamp("#EF4444FF"),
|
||||||
|
// orange: colorRamp("#F97316FF"),
|
||||||
|
// amber: colorRamp("#F59E0BFF"),
|
||||||
|
// yellow: colorRamp("#EAB308FF"),
|
||||||
|
// lime: colorRamp("#84CC16FF"),
|
||||||
|
// green: colorRamp("#22C55EFF"),
|
||||||
|
// emerald: colorRamp("#10B981FF"),
|
||||||
|
// teal: colorRamp("#14B8A6FF"),
|
||||||
|
// cyan: colorRamp("#06BBD4FF"),
|
||||||
|
// sky: colorRamp("#0EA5E9FF"),
|
||||||
|
// blue: colorRamp("#3B82F6FF"),
|
||||||
|
// indigo: colorRamp("#6366F1FF"),
|
||||||
|
// violet: colorRamp("#8B5CF6FF"),
|
||||||
|
// purple: colorRamp("#A855F7FF"),
|
||||||
|
// fuschia: colorRamp("#D946E4FF"),
|
||||||
|
// pink: colorRamp("#EC4899FF"),
|
||||||
|
// }
|
|
@ -4,7 +4,6 @@ import {
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
border,
|
border,
|
||||||
player,
|
player,
|
||||||
modalShadow,
|
|
||||||
text,
|
text,
|
||||||
TextColor,
|
TextColor,
|
||||||
popoverShadow
|
popoverShadow
|
||||||
|
@ -80,15 +79,15 @@ export default function chatPanel(theme: Theme) {
|
||||||
...message,
|
...message,
|
||||||
body: {
|
body: {
|
||||||
...message.body,
|
...message.body,
|
||||||
color: theme.textColor.muted.value,
|
color: theme.textColor.muted,
|
||||||
},
|
},
|
||||||
sender: {
|
sender: {
|
||||||
...message.sender,
|
...message.sender,
|
||||||
color: theme.textColor.muted.value,
|
color: theme.textColor.muted,
|
||||||
},
|
},
|
||||||
timestamp: {
|
timestamp: {
|
||||||
...message.timestamp,
|
...message.timestamp,
|
||||||
color: theme.textColor.muted.value,
|
color: theme.textColor.muted,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
inputEditor: {
|
inputEditor: {
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import chroma from "chroma-js";
|
|
||||||
import { isIPv4 } from "net";
|
|
||||||
import Theme, { BackgroundColorSet } from "../themes/common/theme";
|
import Theme, { BackgroundColorSet } from "../themes/common/theme";
|
||||||
import { fontFamilies, fontSizes, FontWeight } from "../tokens";
|
import { fontFamilies, fontSizes, FontWeight } from "../common";
|
||||||
import { Color } from "../utils/color";
|
|
||||||
|
|
||||||
export type TextColor = keyof Theme["textColor"];
|
export type TextColor = keyof Theme["textColor"];
|
||||||
export function text(
|
export function text(
|
||||||
|
@ -15,16 +12,16 @@ export function text(
|
||||||
underline?: boolean;
|
underline?: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
let size = fontSizes[properties?.size || "sm"].value;
|
let size = fontSizes[properties?.size || "sm"];
|
||||||
return {
|
return {
|
||||||
family: fontFamilies[fontFamily].value,
|
family: fontFamilies[fontFamily],
|
||||||
color: theme.textColor[color].value,
|
color: theme.textColor[color],
|
||||||
...properties,
|
...properties,
|
||||||
size,
|
size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function textColor(theme: Theme, color: TextColor) {
|
export function textColor(theme: Theme, color: TextColor) {
|
||||||
return theme.textColor[color].value;
|
return theme.textColor[color];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BorderColor = keyof Theme["borderColor"];
|
export type BorderColor = keyof Theme["borderColor"];
|
||||||
|
@ -48,19 +45,19 @@ export function border(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function borderColor(theme: Theme, color: BorderColor) {
|
export function borderColor(theme: Theme, color: BorderColor) {
|
||||||
return theme.borderColor[color].value;
|
return theme.borderColor[color];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IconColor = keyof Theme["iconColor"];
|
export type IconColor = keyof Theme["iconColor"];
|
||||||
export function iconColor(theme: Theme, color: IconColor) {
|
export function iconColor(theme: Theme, color: IconColor) {
|
||||||
return theme.iconColor[color].value;
|
return theme.iconColor[color];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PlayerIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
export type PlayerIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||||
export interface Player {
|
export interface Player {
|
||||||
selection: {
|
selection: {
|
||||||
cursor: Color;
|
cursor: string;
|
||||||
selection: Color;
|
selection: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function player(
|
export function player(
|
||||||
|
@ -69,8 +66,8 @@ export function player(
|
||||||
): Player {
|
): Player {
|
||||||
return {
|
return {
|
||||||
selection: {
|
selection: {
|
||||||
cursor: theme.player[playerNumber].cursorColor.value,
|
cursor: theme.player[playerNumber].cursorColor,
|
||||||
selection: theme.player[playerNumber].selectionColor.value,
|
selection: theme.player[playerNumber].selectionColor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -81,14 +78,14 @@ export function backgroundColor(
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
name: BackgroundColor,
|
name: BackgroundColor,
|
||||||
state?: BackgroundState,
|
state?: BackgroundState,
|
||||||
): Color {
|
): string {
|
||||||
return theme.backgroundColor[name][state || "base"].value;
|
return theme.backgroundColor[name][state || "base"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modalShadow(theme: Theme) {
|
export function modalShadow(theme: Theme) {
|
||||||
return {
|
return {
|
||||||
blur: 16,
|
blur: 16,
|
||||||
color: theme.shadow.value,
|
color: theme.shadow,
|
||||||
offset: [0, 2],
|
offset: [0, 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -96,7 +93,7 @@ export function modalShadow(theme: Theme) {
|
||||||
export function popoverShadow(theme: Theme) {
|
export function popoverShadow(theme: Theme) {
|
||||||
return {
|
return {
|
||||||
blur: 4,
|
blur: 4,
|
||||||
color: theme.shadow.value,
|
color: theme.shadow,
|
||||||
offset: [1, 2],
|
offset: [1, 2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,28 +43,28 @@ export default function editor(theme: Theme) {
|
||||||
for (const syntaxKey in theme.syntax) {
|
for (const syntaxKey in theme.syntax) {
|
||||||
const style = theme.syntax[syntaxKey];
|
const style = theme.syntax[syntaxKey];
|
||||||
syntax[syntaxKey] = {
|
syntax[syntaxKey] = {
|
||||||
color: style.color.value,
|
color: style.color,
|
||||||
weight: style.weight.value,
|
weight: style.weight,
|
||||||
underline: style.underline,
|
underline: style.underline,
|
||||||
italic: style.italic,
|
italic: style.italic,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
textColor: theme.syntax.primary.color.value,
|
textColor: theme.syntax.primary.color,
|
||||||
background: backgroundColor(theme, 500),
|
background: backgroundColor(theme, 500),
|
||||||
activeLineBackground: theme.editor.line.active.value,
|
activeLineBackground: theme.editor.line.active,
|
||||||
codeActionsIndicator: iconColor(theme, "muted"),
|
codeActionsIndicator: iconColor(theme, "muted"),
|
||||||
diffBackgroundDeleted: backgroundColor(theme, "error"),
|
diffBackgroundDeleted: backgroundColor(theme, "error"),
|
||||||
diffBackgroundInserted: backgroundColor(theme, "ok"),
|
diffBackgroundInserted: backgroundColor(theme, "ok"),
|
||||||
documentHighlightReadBackground: theme.editor.highlight.occurrence.value,
|
documentHighlightReadBackground: theme.editor.highlight.occurrence,
|
||||||
documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence.value,
|
documentHighlightWriteBackground: theme.editor.highlight.activeOccurrence,
|
||||||
errorColor: theme.textColor.error.value,
|
errorColor: theme.textColor.error,
|
||||||
gutterBackground: backgroundColor(theme, 500),
|
gutterBackground: backgroundColor(theme, 500),
|
||||||
gutterPaddingFactor: 3.5,
|
gutterPaddingFactor: 3.5,
|
||||||
highlightedLineBackground: theme.editor.line.highlighted.value,
|
highlightedLineBackground: theme.editor.line.highlighted,
|
||||||
lineNumber: theme.editor.gutter.primary.value,
|
lineNumber: theme.editor.gutter.primary,
|
||||||
lineNumberActive: theme.editor.gutter.active.value,
|
lineNumberActive: theme.editor.gutter.active,
|
||||||
renameFade: 0.6,
|
renameFade: 0.6,
|
||||||
unnecessaryCodeFade: 0.5,
|
unnecessaryCodeFade: 0.5,
|
||||||
selection: player(theme, 1).selection,
|
selection: player(theme, 1).selection,
|
||||||
|
@ -120,7 +120,7 @@ export default function editor(theme: Theme) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
diagnosticPathHeader: {
|
diagnosticPathHeader: {
|
||||||
background: theme.editor.line.active.value,
|
background: theme.editor.line.active,
|
||||||
textScaleFactor: 0.857,
|
textScaleFactor: 0.857,
|
||||||
filename: text(theme, "mono", "primary", { size: "sm" }),
|
filename: text(theme, "mono", "primary", { size: "sm" }),
|
||||||
path: {
|
path: {
|
||||||
|
@ -139,6 +139,10 @@ export default function editor(theme: Theme) {
|
||||||
invalidInformationDiagnostic: diagnostic(theme, "muted"),
|
invalidInformationDiagnostic: diagnostic(theme, "muted"),
|
||||||
invalidWarningDiagnostic: diagnostic(theme, "muted"),
|
invalidWarningDiagnostic: diagnostic(theme, "muted"),
|
||||||
hover_popover: hoverPopover(theme),
|
hover_popover: hoverPopover(theme),
|
||||||
|
link_definition: {
|
||||||
|
color: theme.syntax.linkUri.color,
|
||||||
|
underline: theme.syntax.linkUri.underline,
|
||||||
|
},
|
||||||
jumpIcon: {
|
jumpIcon: {
|
||||||
color: iconColor(theme, "muted"),
|
color: iconColor(theme, "muted"),
|
||||||
iconWidth: 20,
|
iconWidth: 20,
|
||||||
|
|
|
@ -22,6 +22,6 @@ export default function HoverPopover(theme: Theme) {
|
||||||
padding: { top: 4 },
|
padding: { top: 4 },
|
||||||
},
|
},
|
||||||
prose: text(theme, "sans", "primary", { "size": "sm" }),
|
prose: text(theme, "sans", "primary", { "size": "sm" }),
|
||||||
highlight: theme.editor.highlight.occurrence.value,
|
highlight: theme.editor.highlight.occurrence,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,7 @@ export default function search(theme: Theme) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
matchBackground: theme.editor.highlight.match.value,
|
matchBackground: theme.editor.highlight.match,
|
||||||
tabIconSpacing: 8,
|
tabIconSpacing: 8,
|
||||||
tabIconWidth: 14,
|
tabIconWidth: 14,
|
||||||
optionButton: {
|
optionButton: {
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default function updateNotification(theme: Theme): Object {
|
||||||
...text(theme, "sans", "secondary", { size: "xs" }),
|
...text(theme, "sans", "secondary", { size: "xs" }),
|
||||||
margin: { left: headerPadding, top: 6, bottom: 6 },
|
margin: { left: headerPadding, top: 6, bottom: 6 },
|
||||||
hover: {
|
hover: {
|
||||||
color: theme.textColor["active"].value
|
color: theme.textColor["active"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton: {
|
dismissButton: {
|
||||||
|
|
|
@ -147,7 +147,7 @@ export default function workspace(theme: Theme) {
|
||||||
},
|
},
|
||||||
disconnectedOverlay: {
|
disconnectedOverlay: {
|
||||||
...text(theme, "sans", "active"),
|
...text(theme, "sans", "active"),
|
||||||
background: withOpacity(theme.backgroundColor[500].base, 0.8).value,
|
background: withOpacity(theme.backgroundColor[500].base, 0.8),
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
margin: { top: 10 },
|
margin: { top: 10 },
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import chroma, { Color, Scale } from "chroma-js";
|
import chroma, { Color, Scale } from "chroma-js";
|
||||||
import { color, ColorToken, fontWeights, NumberToken } from "../../tokens";
|
import { fontWeights, } from "../../common";
|
||||||
import { withOpacity } from "../../utils/color";
|
import { withOpacity } from "../../utils/color";
|
||||||
import Theme, { buildPlayer, Syntax } from "./theme";
|
import Theme, { buildPlayer, Syntax } from "./theme";
|
||||||
|
|
||||||
|
@ -26,10 +26,10 @@ export function createTheme(
|
||||||
|
|
||||||
let blend = isLight ? 0.12 : 0.24;
|
let blend = isLight ? 0.12 : 0.24;
|
||||||
|
|
||||||
function sample(ramp: Scale, index: number): ColorToken {
|
function sample(ramp: Scale, index: number): string {
|
||||||
return color(ramp(index).hex());
|
return ramp(index).hex();
|
||||||
}
|
}
|
||||||
const darkest = color(ramps.neutral(isLight ? 7 : 0).hex());
|
const darkest = ramps.neutral(isLight ? 7 : 0).hex();
|
||||||
|
|
||||||
const backgroundColor = {
|
const backgroundColor = {
|
||||||
// Title bar
|
// Title bar
|
||||||
|
@ -232,7 +232,7 @@ export function createTheme(
|
||||||
};
|
};
|
||||||
|
|
||||||
const shadow = withOpacity(
|
const shadow = withOpacity(
|
||||||
color(ramps.neutral(isLight ? 7 : 0).darken().hex()),
|
ramps.neutral(isLight ? 7 : 0).darken().hex(),
|
||||||
blend);
|
blend);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { ColorToken, FontWeightToken, NumberToken } from "../../tokens";
|
import { FontWeight } from "../../common";
|
||||||
import { withOpacity } from "../../utils/color";
|
import { withOpacity } from "../../utils/color";
|
||||||
|
|
||||||
export interface SyntaxHighlightStyle {
|
export interface SyntaxHighlightStyle {
|
||||||
color: ColorToken;
|
color: string;
|
||||||
weight?: FontWeightToken;
|
weight?: FontWeight;
|
||||||
underline?: boolean;
|
underline?: boolean;
|
||||||
italic?: boolean;
|
italic?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Player {
|
export interface Player {
|
||||||
baseColor: ColorToken;
|
baseColor: string;
|
||||||
cursorColor: ColorToken;
|
cursorColor: string;
|
||||||
selectionColor: ColorToken;
|
selectionColor: string;
|
||||||
borderColor: ColorToken;
|
borderColor: string;
|
||||||
}
|
}
|
||||||
export function buildPlayer(
|
export function buildPlayer(
|
||||||
color: ColorToken,
|
color: string,
|
||||||
cursorOpacity?: number,
|
cursorOpacity?: number,
|
||||||
selectionOpacity?: number,
|
selectionOpacity?: number,
|
||||||
borderOpacity?: number
|
borderOpacity?: number
|
||||||
|
@ -29,9 +29,9 @@ export function buildPlayer(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackgroundColorSet {
|
export interface BackgroundColorSet {
|
||||||
base: ColorToken;
|
base: string;
|
||||||
hovered: ColorToken;
|
hovered: string;
|
||||||
active: ColorToken;
|
active: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Syntax {
|
export interface Syntax {
|
||||||
|
@ -81,64 +81,64 @@ export default interface Theme {
|
||||||
info: BackgroundColorSet;
|
info: BackgroundColorSet;
|
||||||
};
|
};
|
||||||
borderColor: {
|
borderColor: {
|
||||||
primary: ColorToken;
|
primary: string;
|
||||||
secondary: ColorToken;
|
secondary: string;
|
||||||
muted: ColorToken;
|
muted: string;
|
||||||
active: ColorToken;
|
active: string;
|
||||||
/**
|
/**
|
||||||
* Used for rendering borders on top of media like avatars, images, video, etc.
|
* Used for rendering borders on top of media like avatars, images, video, etc.
|
||||||
*/
|
*/
|
||||||
onMedia: ColorToken;
|
onMedia: string;
|
||||||
ok: ColorToken;
|
ok: string;
|
||||||
error: ColorToken;
|
error: string;
|
||||||
warning: ColorToken;
|
warning: string;
|
||||||
info: ColorToken;
|
info: string;
|
||||||
};
|
};
|
||||||
textColor: {
|
textColor: {
|
||||||
primary: ColorToken;
|
primary: string;
|
||||||
secondary: ColorToken;
|
secondary: string;
|
||||||
muted: ColorToken;
|
muted: string;
|
||||||
placeholder: ColorToken;
|
placeholder: string;
|
||||||
active: ColorToken;
|
active: string;
|
||||||
feature: ColorToken;
|
feature: string;
|
||||||
ok: ColorToken;
|
ok: string;
|
||||||
error: ColorToken;
|
error: string;
|
||||||
warning: ColorToken;
|
warning: string;
|
||||||
info: ColorToken;
|
info: string;
|
||||||
onMedia: ColorToken;
|
onMedia: string;
|
||||||
};
|
};
|
||||||
iconColor: {
|
iconColor: {
|
||||||
primary: ColorToken;
|
primary: string;
|
||||||
secondary: ColorToken;
|
secondary: string;
|
||||||
muted: ColorToken;
|
muted: string;
|
||||||
placeholder: ColorToken;
|
placeholder: string;
|
||||||
active: ColorToken;
|
active: string;
|
||||||
feature: ColorToken;
|
feature: string;
|
||||||
ok: ColorToken;
|
ok: string;
|
||||||
error: ColorToken;
|
error: string;
|
||||||
warning: ColorToken;
|
warning: string;
|
||||||
info: ColorToken;
|
info: string;
|
||||||
};
|
};
|
||||||
editor: {
|
editor: {
|
||||||
background: ColorToken;
|
background: string;
|
||||||
indent_guide: ColorToken;
|
indent_guide: string;
|
||||||
indent_guide_active: ColorToken;
|
indent_guide_active: string;
|
||||||
line: {
|
line: {
|
||||||
active: ColorToken;
|
active: string;
|
||||||
highlighted: ColorToken;
|
highlighted: string;
|
||||||
};
|
};
|
||||||
highlight: {
|
highlight: {
|
||||||
selection: ColorToken;
|
selection: string;
|
||||||
occurrence: ColorToken;
|
occurrence: string;
|
||||||
activeOccurrence: ColorToken;
|
activeOccurrence: string;
|
||||||
matchingBracket: ColorToken;
|
matchingBracket: string;
|
||||||
match: ColorToken;
|
match: string;
|
||||||
activeMatch: ColorToken;
|
activeMatch: string;
|
||||||
related: ColorToken;
|
related: string;
|
||||||
};
|
};
|
||||||
gutter: {
|
gutter: {
|
||||||
primary: ColorToken;
|
primary: string;
|
||||||
active: ColorToken;
|
active: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -154,5 +154,5 @@ export default interface Theme {
|
||||||
7: Player;
|
7: Player;
|
||||||
8: Player;
|
8: Player;
|
||||||
},
|
},
|
||||||
shadow: ColorToken;
|
shadow: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
import { colorRamp } from "./utils/color";
|
|
||||||
|
|
||||||
interface Token<V, T> {
|
|
||||||
value: V,
|
|
||||||
type: T
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FontFamily = string;
|
|
||||||
export type FontFamilyToken = Token<FontFamily, "fontFamily">;
|
|
||||||
function fontFamily(value: FontFamily): FontFamilyToken {
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
type: "fontFamily"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const fontFamilies = {
|
|
||||||
sans: fontFamily("Zed Sans"),
|
|
||||||
mono: fontFamily("Zed Mono"),
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FontSize = number;
|
|
||||||
export type FontSizeToken = Token<FontSize, "fontSize">;
|
|
||||||
function fontSize(value: FontSize) {
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
type: "fontSize"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export const fontSizes = {
|
|
||||||
"3xs": fontSize(8),
|
|
||||||
"2xs": fontSize(10),
|
|
||||||
xs: fontSize(12),
|
|
||||||
sm: fontSize(14),
|
|
||||||
md: fontSize(16),
|
|
||||||
lg: fontSize(18),
|
|
||||||
xl: fontSize(20),
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FontWeight =
|
|
||||||
| "thin"
|
|
||||||
| "extra_light"
|
|
||||||
| "light"
|
|
||||||
| "normal"
|
|
||||||
| "medium"
|
|
||||||
| "semibold"
|
|
||||||
| "bold"
|
|
||||||
| "extra_bold"
|
|
||||||
| "black";
|
|
||||||
export type FontWeightToken = Token<FontWeight, "fontWeight">;
|
|
||||||
function fontWeight(value: FontWeight): FontWeightToken {
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
type: "fontWeight"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export const fontWeights = {
|
|
||||||
"thin": fontWeight("thin"),
|
|
||||||
"extra_light": fontWeight("extra_light"),
|
|
||||||
"light": fontWeight("light"),
|
|
||||||
"normal": fontWeight("normal"),
|
|
||||||
"medium": fontWeight("medium"),
|
|
||||||
"semibold": fontWeight("semibold"),
|
|
||||||
"bold": fontWeight("bold"),
|
|
||||||
"extra_bold": fontWeight("extra_bold"),
|
|
||||||
"black": fontWeight("black"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard size unit used for paddings, margins, borders, etc.
|
|
||||||
|
|
||||||
export type Size = number
|
|
||||||
|
|
||||||
export type SizeToken = Token<Size, "size">;
|
|
||||||
function size(value: Size): SizeToken {
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
type: "size"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sizes = {
|
|
||||||
px: size(1),
|
|
||||||
xs: size(2),
|
|
||||||
sm: size(4),
|
|
||||||
md: size(6),
|
|
||||||
lg: size(8),
|
|
||||||
xl: size(12),
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Color = string;
|
|
||||||
export interface ColorToken {
|
|
||||||
value: Color,
|
|
||||||
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"),
|
|
||||||
red: colorRamp("#EF4444FF"),
|
|
||||||
orange: colorRamp("#F97316FF"),
|
|
||||||
amber: colorRamp("#F59E0BFF"),
|
|
||||||
yellow: colorRamp("#EAB308FF"),
|
|
||||||
lime: colorRamp("#84CC16FF"),
|
|
||||||
green: colorRamp("#22C55EFF"),
|
|
||||||
emerald: colorRamp("#10B981FF"),
|
|
||||||
teal: colorRamp("#14B8A6FF"),
|
|
||||||
cyan: colorRamp("#06BBD4FF"),
|
|
||||||
sky: colorRamp("#0EA5E9FF"),
|
|
||||||
blue: colorRamp("#3B82F6FF"),
|
|
||||||
indigo: colorRamp("#6366F1FF"),
|
|
||||||
violet: colorRamp("#8B5CF6FF"),
|
|
||||||
purple: colorRamp("#A855F7FF"),
|
|
||||||
fuschia: colorRamp("#D946E4FF"),
|
|
||||||
pink: colorRamp("#EC4899FF"),
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NumberToken = Token<number, "number">;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fontFamilies,
|
|
||||||
fontSizes,
|
|
||||||
fontWeights,
|
|
||||||
size,
|
|
||||||
colors,
|
|
||||||
};
|
|
|
@ -1,52 +1,5 @@
|
||||||
import chroma, { Scale } from "chroma-js";
|
import chroma from "chroma-js";
|
||||||
import { ColorToken } from "../tokens";
|
|
||||||
|
|
||||||
export type Color = string;
|
export function withOpacity(color: string, opacity: number): string {
|
||||||
export type ColorRampStep = { value: Color; type: "color"; description: string };
|
return chroma(color).alpha(opacity).hex();
|
||||||
export type ColorRamp = {
|
|
||||||
[index: number]: ColorRampStep;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function colorRamp(
|
|
||||||
color: Color | [Color, Color],
|
|
||||||
options?: { steps?: number; increment?: number; }
|
|
||||||
): ColorRamp {
|
|
||||||
let scale: Scale;
|
|
||||||
if (Array.isArray(color)) {
|
|
||||||
const [startColor, endColor] = color;
|
|
||||||
scale = chroma.scale([startColor, endColor]);
|
|
||||||
} else {
|
|
||||||
let hue = Math.round(chroma(color).hsl()[0]);
|
|
||||||
let startColor = chroma.hsl(hue, 0.88, 0.96);
|
|
||||||
let endColor = chroma.hsl(hue, 0.68, 0.12);
|
|
||||||
scale = chroma
|
|
||||||
.scale([startColor, color, endColor])
|
|
||||||
.domain([0, 0.5, 1])
|
|
||||||
.mode("hsl")
|
|
||||||
.gamma(1)
|
|
||||||
// .correctLightness(true)
|
|
||||||
.padding([0, 0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ramp: ColorRamp = {};
|
|
||||||
const steps = options?.steps || 10;
|
|
||||||
const increment = options?.increment || 100;
|
|
||||||
|
|
||||||
scale.colors(steps, "hex").forEach((color, ix) => {
|
|
||||||
const step = ix * increment;
|
|
||||||
ramp[step] = {
|
|
||||||
value: color,
|
|
||||||
description: `Step: ${step}`,
|
|
||||||
type: "color",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return ramp;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function withOpacity(color: ColorToken, opacity: number): ColorToken {
|
|
||||||
return {
|
|
||||||
...color,
|
|
||||||
value: chroma(color.value).alpha(opacity).hex()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue