feat: syntax highlighting for diff view
This commit is contained in:
@@ -513,7 +513,9 @@ impl query::QueryFn for FetchPullRequestFileTree {
|
|||||||
edge.node.map(|node| ChangedFile {
|
edge.node.map(|node| ChangedFile {
|
||||||
cursor,
|
cursor,
|
||||||
change_type: match node.change_type {
|
change_type: match node.change_type {
|
||||||
| pull_request_file_tree_query::PatchStatus::ADDED => ChangeType::Added,
|
| pull_request_file_tree_query::PatchStatus::ADDED => {
|
||||||
|
ChangeType::Added
|
||||||
|
}
|
||||||
| pull_request_file_tree_query::PatchStatus::MODIFIED => {
|
| pull_request_file_tree_query::PatchStatus::MODIFIED => {
|
||||||
ChangeType::Modified
|
ChangeType::Modified
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,29 +463,35 @@ mod tests {
|
|||||||
.expect("third timeline fixture json should parse");
|
.expect("third timeline fixture json should parse");
|
||||||
|
|
||||||
let first_page_nodes = match first_page.node.as_ref() {
|
let first_page_nodes = match first_page.node.as_ref() {
|
||||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => pull_request
|
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||||
|
pull_request
|
||||||
.timeline_items
|
.timeline_items
|
||||||
.nodes
|
.nodes
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("first timeline fixture page should contain timeline nodes"),
|
.expect("first timeline fixture page should contain timeline nodes")
|
||||||
|
}
|
||||||
| _ => panic!("first timeline fixture page should resolve to a pull request node"),
|
| _ => panic!("first timeline fixture page should resolve to a pull request node"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let second_page_nodes = match second_page.node.as_ref() {
|
let second_page_nodes = match second_page.node.as_ref() {
|
||||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => pull_request
|
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||||
|
pull_request
|
||||||
.timeline_items
|
.timeline_items
|
||||||
.nodes
|
.nodes
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("second timeline fixture page should contain timeline nodes"),
|
.expect("second timeline fixture page should contain timeline nodes")
|
||||||
|
}
|
||||||
| _ => panic!("second timeline fixture page should resolve to a pull request node"),
|
| _ => panic!("second timeline fixture page should resolve to a pull request node"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let third_page_nodes = match third_page.node.as_ref() {
|
let third_page_nodes = match third_page.node.as_ref() {
|
||||||
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => pull_request
|
| Some(issues::PullRequestTimelineResponseNode::PullRequest(pull_request)) => {
|
||||||
|
pull_request
|
||||||
.timeline_items
|
.timeline_items
|
||||||
.nodes
|
.nodes
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("third timeline fixture page should contain timeline nodes"),
|
.expect("third timeline fixture page should contain timeline nodes")
|
||||||
|
}
|
||||||
| _ => panic!("third timeline fixture page should resolve to a pull request node"),
|
| _ => panic!("third timeline fixture page should resolve to a pull request node"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
use std::{num::NonZeroUsize, rc::Rc, sync::Arc};
|
use std::{num::NonZeroUsize, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{IntoElement, ParentElement, Refineable, Styled, div, list, prelude::FluentBuilder, px};
|
||||||
IntoElement, ParentElement, Refineable, Styled, div, list, prelude::FluentBuilder, px, rems,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::app;
|
use crate::app;
|
||||||
|
|
||||||
#[derive(gpui::IntoElement, Clone)]
|
#[derive(gpui::IntoElement)]
|
||||||
pub(crate) struct CodeLine {
|
pub(crate) struct CodeLine {
|
||||||
line_number: Option<NonZeroUsize>,
|
line_number: Option<NonZeroUsize>,
|
||||||
content: Option<gpui::SharedString>,
|
content: Option<gpui::AnyElement>,
|
||||||
diff_marker: CodeLineMarker,
|
diff_marker: CodeLineMarker,
|
||||||
gutter_width: gpui::Pixels,
|
gutter_width: gpui::Pixels,
|
||||||
style: gpui::StyleRefinement,
|
style: gpui::StyleRefinement,
|
||||||
@@ -45,18 +43,29 @@ pub(crate) fn code_line(
|
|||||||
) -> CodeLine {
|
) -> CodeLine {
|
||||||
CodeLine {
|
CodeLine {
|
||||||
line_number: line_index.map(|i| unsafe { NonZeroUsize::new_unchecked(i + 1) }),
|
line_number: line_index.map(|i| unsafe { NonZeroUsize::new_unchecked(i + 1) }),
|
||||||
content,
|
content: content.map(|it| it.into_any_element()),
|
||||||
diff_marker: marker,
|
diff_marker: marker,
|
||||||
gutter_width: px(0.),
|
gutter_width: px(0.),
|
||||||
style: gpui::StyleRefinement::default(),
|
style: gpui::StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeViewContent {
|
pub(crate) fn code_line_with_highlights(
|
||||||
pub(crate) fn new(lines: Vec<CodeLine>) -> Self {
|
line_index: Option<usize>,
|
||||||
Self {
|
content: Option<gpui::SharedString>,
|
||||||
lines: lines.into(),
|
highlights: impl IntoIterator<Item = (std::ops::Range<usize>, gpui::HighlightStyle)>,
|
||||||
}
|
marker: CodeLineMarker,
|
||||||
|
) -> CodeLine {
|
||||||
|
CodeLine {
|
||||||
|
line_number: line_index.map(|i| unsafe { NonZeroUsize::new_unchecked(i + 1) }),
|
||||||
|
content: content.map(|it| {
|
||||||
|
gpui::StyledText::new(it)
|
||||||
|
.with_highlights(highlights)
|
||||||
|
.into_any_element()
|
||||||
|
}),
|
||||||
|
diff_marker: marker,
|
||||||
|
gutter_width: px(0.),
|
||||||
|
style: gpui::StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +98,7 @@ impl gpui::RenderOnce for CodeView {
|
|||||||
|
|
||||||
println!("gutter width {}", gutter_width);
|
println!("gutter width {}", gutter_width);
|
||||||
|
|
||||||
list(self.state.0, move |i, _window, _app| {
|
list(self.state.0, move |i, _window, _app| todo!())
|
||||||
let line = self.content.lines[i].clone();
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_row()
|
|
||||||
.items_start()
|
|
||||||
.w_full()
|
|
||||||
.child(line.gutter_width(gutter_width))
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||||
|
|
||||||
use gpui::{IntoElement, ParentElement, Styled, div, list, px};
|
use gpui::{HighlightStyle, IntoElement, ParentElement, Styled, div, list, px};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::code_view::{self, CodeLine, code_line},
|
component::code_view::{self, CodeLine, code_line, code_line_with_highlights},
|
||||||
util::{self, str::ToSharedString},
|
util::{self, str::ToSharedString},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,7 +14,13 @@ pub(crate) struct DiffView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DiffViewState(gpui::ListState);
|
pub(crate) struct DiffViewState(Rc<RefCell<DiffViewStateInner>>);
|
||||||
|
|
||||||
|
struct DiffViewStateInner {
|
||||||
|
list_state: gpui::ListState,
|
||||||
|
old_side_highlights: Option<util::syntax_highlight::HighlightedContent>,
|
||||||
|
new_side_highlights: Option<util::syntax_highlight::HighlightedContent>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DiffViewContent {
|
pub(crate) struct DiffViewContent {
|
||||||
@@ -23,6 +29,7 @@ pub(crate) struct DiffViewContent {
|
|||||||
|
|
||||||
#[derive(Clone, gpui::IntoElement)]
|
#[derive(Clone, gpui::IntoElement)]
|
||||||
struct DiffRow {
|
struct DiffRow {
|
||||||
|
state: DiffViewState,
|
||||||
line: util::diff::DiffLine,
|
line: util::diff::DiffLine,
|
||||||
old_side_gutter_width: gpui::Pixels,
|
old_side_gutter_width: gpui::Pixels,
|
||||||
new_side_gutter_width: gpui::Pixels,
|
new_side_gutter_width: gpui::Pixels,
|
||||||
@@ -40,22 +47,36 @@ impl From<Arc<util::diff::ContentDiff>> for DiffViewContent {
|
|||||||
|
|
||||||
impl DiffViewState {
|
impl DiffViewState {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self(gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)))
|
Self(Rc::new(RefCell::new(DiffViewStateInner {
|
||||||
|
list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)),
|
||||||
|
old_side_highlights: None,
|
||||||
|
new_side_highlights: None,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset(&mut self, line_count: usize) {
|
pub(crate) fn reset(&mut self, line_count: usize) {
|
||||||
self.0.reset(line_count);
|
self.0.borrow().list_state.reset(line_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_old_side_highlights(
|
||||||
|
&mut self,
|
||||||
|
highlights: util::syntax_highlight::HighlightedContent,
|
||||||
|
) {
|
||||||
|
self.0.borrow_mut().old_side_highlights = Some(highlights);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_new_side_highlights(
|
||||||
|
&mut self,
|
||||||
|
highlights: util::syntax_highlight::HighlightedContent,
|
||||||
|
) {
|
||||||
|
self.0.borrow_mut().new_side_highlights = Some(highlights);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui::RenderOnce for DiffView {
|
impl gpui::RenderOnce for DiffView {
|
||||||
fn render(self, window: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
fn render(self, window: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||||
let (old_digits, new_digits) = self
|
let old_digits = self.content.diff.old_line_count.to_string().len();
|
||||||
.content
|
let new_digits = self.content.diff.new_line_count.to_string().len();
|
||||||
.diff
|
|
||||||
.last()
|
|
||||||
.map(|l| (l.old_line.to_string().len(), l.new_line.to_string().len()))
|
|
||||||
.unwrap_or((1, 1));
|
|
||||||
|
|
||||||
let text_style = window.text_style();
|
let text_style = window.text_style();
|
||||||
let font_size = text_style.font_size.to_pixels(window.rem_size());
|
let font_size = text_style.font_size.to_pixels(window.rem_size());
|
||||||
@@ -69,8 +90,11 @@ impl gpui::RenderOnce for DiffView {
|
|||||||
let old_side_gutter_width = ch * old_digits;
|
let old_side_gutter_width = ch * old_digits;
|
||||||
let new_side_gutter_width = ch * new_digits;
|
let new_side_gutter_width = ch * new_digits;
|
||||||
|
|
||||||
list(self.state.0, move |i, _, cx| {
|
let list_state = self.state.0.borrow().list_state.clone();
|
||||||
|
|
||||||
|
list(list_state, move |i, _, _| {
|
||||||
DiffRow {
|
DiffRow {
|
||||||
|
state: self.state.clone(),
|
||||||
line: self.content.diff.get(i).clone(),
|
line: self.content.diff.get(i).clone(),
|
||||||
old_side_gutter_width,
|
old_side_gutter_width,
|
||||||
new_side_gutter_width,
|
new_side_gutter_width,
|
||||||
@@ -84,35 +108,69 @@ impl gpui::RenderOnce for DiffView {
|
|||||||
|
|
||||||
impl DiffRow {
|
impl DiffRow {
|
||||||
fn old_code_line(&self) -> CodeLine {
|
fn old_code_line(&self) -> CodeLine {
|
||||||
code_line(
|
let state = self.state.0.borrow();
|
||||||
Some(self.line.old_line),
|
|
||||||
self.line
|
let content = self
|
||||||
|
.line
|
||||||
.old_content
|
.old_content
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|it| it.to_shared_string()),
|
.map(|it| it.to_shared_string());
|
||||||
match self.line.op {
|
|
||||||
|
let marker = match self.line.op {
|
||||||
| util::diff::Op::Equal => code_view::CodeLineMarker::Unchanged,
|
| util::diff::Op::Equal => code_view::CodeLineMarker::Unchanged,
|
||||||
|
// inserting on new side, so placeholder on old side
|
||||||
| util::diff::Op::Insert => code_view::CodeLineMarker::Placeholder,
|
| util::diff::Op::Insert => code_view::CodeLineMarker::Placeholder,
|
||||||
| util::diff::Op::Replace | util::diff::Op::Delete => {
|
// old side replaced, so delete
|
||||||
code_view::CodeLineMarker::Deleted
|
| util::diff::Op::Replace | util::diff::Op::Delete => code_view::CodeLineMarker::Deleted,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.line.old_line.and_then(|line| {
|
||||||
|
state
|
||||||
|
.old_side_highlights
|
||||||
|
.as_ref()
|
||||||
|
.map(|it| it.highlights_at_line(line))
|
||||||
|
}) {
|
||||||
|
| Some(highlights) => code_line_with_highlights(
|
||||||
|
self.line.new_line,
|
||||||
|
content,
|
||||||
|
highlights.iter().cloned(),
|
||||||
|
marker,
|
||||||
|
),
|
||||||
|
|
||||||
|
| None => code_line(self.line.new_line, content, marker),
|
||||||
}
|
}
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_code_line(&self) -> CodeLine {
|
fn new_code_line(&self) -> CodeLine {
|
||||||
code_line(
|
let state = self.state.0.borrow();
|
||||||
Some(self.line.new_line),
|
|
||||||
self.line
|
let content = self
|
||||||
|
.line
|
||||||
.new_content
|
.new_content
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|it| it.to_shared_string()),
|
.map(|it| it.to_shared_string());
|
||||||
match self.line.op {
|
|
||||||
|
let marker = match self.line.op {
|
||||||
| util::diff::Op::Equal => code_view::CodeLineMarker::Unchanged,
|
| util::diff::Op::Equal => code_view::CodeLineMarker::Unchanged,
|
||||||
| util::diff::Op::Insert | util::diff::Op::Replace => code_view::CodeLineMarker::Added,
|
| util::diff::Op::Insert | util::diff::Op::Replace => code_view::CodeLineMarker::Added,
|
||||||
| util::diff::Op::Delete => code_view::CodeLineMarker::Deleted,
|
| util::diff::Op::Delete => code_view::CodeLineMarker::Deleted,
|
||||||
},
|
};
|
||||||
)
|
|
||||||
|
match self.line.new_line.and_then(|line| {
|
||||||
|
state
|
||||||
|
.new_side_highlights
|
||||||
|
.as_ref()
|
||||||
|
.map(|it| it.highlights_at_line(line))
|
||||||
|
}) {
|
||||||
|
| Some(highlights) => code_line_with_highlights(
|
||||||
|
self.line.new_line,
|
||||||
|
content,
|
||||||
|
highlights.iter().cloned(),
|
||||||
|
marker,
|
||||||
|
),
|
||||||
|
|
||||||
|
| None => code_line(self.line.new_line, content, marker),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -214,8 +214,10 @@ impl MarkdownText {
|
|||||||
if cursor.goto_next_sibling()
|
if cursor.goto_next_sibling()
|
||||||
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes())
|
&& let Ok(src) = cursor.node().utf8_text(content.as_bytes())
|
||||||
{
|
{
|
||||||
links
|
links.push((
|
||||||
.push((node_range!(), gpui::SharedString::from(String::from(src))));
|
node_range!(),
|
||||||
|
gpui::SharedString::from(String::from(src)),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// the link src is invalid, use an empty string as a fallback
|
// the link src is invalid, use an empty string as a fallback
|
||||||
// link on click handler will ignore empty string
|
// link on click handler will ignore empty string
|
||||||
|
|||||||
27
src/query.rs
27
src/query.rs
@@ -1,4 +1,4 @@
|
|||||||
use gpui::{AppContext, BorrowAppContext};
|
use gpui::BorrowAppContext;
|
||||||
use std::{any::Any, borrow::Cow, collections::HashMap, marker::PhantomData, ops::Deref};
|
use std::{any::Any, borrow::Cow, collections::HashMap, marker::PhantomData, ops::Deref};
|
||||||
|
|
||||||
pub(crate) trait QueryFn: Clone + 'static {
|
pub(crate) trait QueryFn: Clone + 'static {
|
||||||
@@ -187,19 +187,28 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn observe_query<E, F>(
|
pub fn watch_query<E, F, H>(query: &Entity<F>, on_notify: H, cx: &mut gpui::Context<E>) -> gpui::Subscription
|
||||||
query: &Entity<F>,
|
|
||||||
mut on_notify: impl FnMut(&mut E, &Entity<F>, &mut gpui::Context<E>) + 'static,
|
|
||||||
cx: &mut gpui::Context<E>,
|
|
||||||
) -> gpui::Subscription
|
|
||||||
where
|
where
|
||||||
E: 'static,
|
E: 'static,
|
||||||
F: QueryFn,
|
F: QueryFn,
|
||||||
|
H: Fn(&mut E, &Entity<F>, &mut gpui::Context<E>) + Clone + 'static,
|
||||||
{
|
{
|
||||||
let q = query.clone();
|
let observed_query = query.clone();
|
||||||
cx.observe(&query, move |this, _, cx| {
|
let sub = cx.observe(query, {
|
||||||
on_notify(this, &q, cx);
|
let on_notify = on_notify.clone();
|
||||||
|
move |this, _, cx| on_notify(this, &observed_query, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let initial_query = query.clone();
|
||||||
|
cx.spawn({
|
||||||
|
let on_notify = on_notify.clone();
|
||||||
|
async move |weak, cx| {
|
||||||
|
let _ = weak.update(cx, |this, cx| on_notify(this, &initial_query, cx));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
sub
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= Store ==================
|
// ================= Store ==================
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
font_icon::{FontIcon, FontIconSvg, font_icon},
|
font_icon::{FontIcon, FontIconSvg, font_icon},
|
||||||
text::text,
|
text::text,
|
||||||
},
|
},
|
||||||
query::{self, QueryStatus, read_query, use_query},
|
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||||
util::str::ToSharedString,
|
util::str::ToSharedString,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,28 +56,44 @@ pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> IssueList {
|
|||||||
|
|
||||||
impl IssueList {
|
impl IssueList {
|
||||||
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
fn on_create(&mut self, cx: &mut gpui::Context<Self>) {
|
||||||
cx.observe(&self.pr_query, |this, _, cx| {
|
let pr_query = self.pr_query.clone();
|
||||||
let data = read_query(&this.pr_query, cx);
|
|
||||||
|
watch_query(&pr_query, Self::sync_pr_query, cx).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_pr_query(
|
||||||
|
&mut self,
|
||||||
|
query: &query::Entity<api::issues::ListPullRequests>,
|
||||||
|
cx: &mut gpui::Context<Self>,
|
||||||
|
) {
|
||||||
|
let data = read_query(query, cx);
|
||||||
if let QueryStatus::Loaded(res) = data {
|
if let QueryStatus::Loaded(res) = data {
|
||||||
let old_len = this.list_state.item_count();
|
let selected_id = self
|
||||||
|
.list_items
|
||||||
|
.iter()
|
||||||
|
.find(|item| item.is_selected)
|
||||||
|
.map(|item| item.id.clone());
|
||||||
|
let old_len = self.list_state.item_count();
|
||||||
let new_len = res.items.len();
|
let new_len = res.items.len();
|
||||||
|
|
||||||
let new_items = res.items.iter().enumerate().map(|(i, it)| IssueListItem {
|
self.list_items = res
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, it)| IssueListItem {
|
||||||
|
is_selected: selected_id.as_ref().is_some_and(|id| *id == it.id),
|
||||||
id: it.id.clone(),
|
id: it.id.clone(),
|
||||||
repo_name: Some(it.repo_slug.to_shared_string()),
|
repo_name: Some(it.repo_slug.to_shared_string()),
|
||||||
title: it.title.to_shared_string(),
|
title: it.title.to_shared_string(),
|
||||||
description: None,
|
description: None,
|
||||||
status: it.state,
|
status: it.state,
|
||||||
is_selected: false,
|
|
||||||
is_last: i == new_len - 1,
|
is_last: i == new_len - 1,
|
||||||
is_draft: it.is_draft,
|
is_draft: it.is_draft,
|
||||||
});
|
|
||||||
|
|
||||||
this.list_items.splice(old_len..old_len, new_items);
|
|
||||||
this.list_state.splice(old_len..old_len, new_len);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.collect();
|
||||||
|
|
||||||
|
self.list_state.splice(0..old_len, new_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {
|
fn on_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ use crate::{
|
|||||||
diff_view::{DiffViewContent, DiffViewState, diff_view},
|
diff_view::{DiffViewContent, DiffViewState, diff_view},
|
||||||
text::text,
|
text::text,
|
||||||
},
|
},
|
||||||
query::{self, QueryStatus, observe_query, read_query, use_query},
|
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||||
|
util,
|
||||||
};
|
};
|
||||||
use gpui::{ParentElement, Styled, div};
|
use gpui::{AppContext, ParentElement, Styled, div};
|
||||||
|
|
||||||
pub(crate) struct PullRequestDiffView {
|
pub(crate) struct PullRequestDiffView {
|
||||||
selected_file_path: Option<Arc<str>>,
|
selected_file_path: Option<Arc<str>>,
|
||||||
@@ -104,23 +105,68 @@ impl PullRequestDiffView {
|
|||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
_ = watch_query(&content_diff_query, Self::sync_content_diff_query, cx).detach();
|
||||||
_ = observe_query(
|
|
||||||
&content_diff_query,
|
|
||||||
|this, query, cx| {
|
|
||||||
if let QueryStatus::Loaded(diff) = read_query(query, cx) {
|
|
||||||
println!("diff len {}", diff.len());
|
|
||||||
this.diff_view_state.reset(diff.len());
|
|
||||||
this.diff_view_content = Some(Arc::clone(diff).into());
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
self.content_diff_query = Some(content_diff_query);
|
self.content_diff_query = Some(content_diff_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_content_diff_query(
|
||||||
|
&mut self,
|
||||||
|
query: &query::Entity<api::repo::FetchFileDiff>,
|
||||||
|
cx: &mut gpui::Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(diff) = {
|
||||||
|
match read_query(query, cx) {
|
||||||
|
| QueryStatus::Loaded(diff) => Some(Arc::clone(diff)),
|
||||||
|
| _ => None,
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
self.load_diff_view(diff, cx);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_diff_view(
|
||||||
|
&mut self,
|
||||||
|
content_diff: Arc<util::diff::ContentDiff>,
|
||||||
|
cx: &mut gpui::Context<Self>,
|
||||||
|
) {
|
||||||
|
let theme = app::current_theme(cx);
|
||||||
|
let old_content = content_diff.old_content.clone();
|
||||||
|
let new_content = content_diff.new_content.clone();
|
||||||
|
|
||||||
|
self.diff_view_state.reset(content_diff.len());
|
||||||
|
self.diff_view_content = Some(content_diff.into());
|
||||||
|
|
||||||
|
let theme_syntax = theme.syntax;
|
||||||
|
|
||||||
|
if let Some(path) = &self.selected_file_path {
|
||||||
|
let path = Arc::clone(&path);
|
||||||
|
let file_type = util::file::file_type_from_path(&path);
|
||||||
|
|
||||||
|
let t1 = cx.background_spawn(async move {
|
||||||
|
util::syntax_highlight::highlight_content(old_content, file_type, &theme_syntax)
|
||||||
|
});
|
||||||
|
let t2 = cx.background_spawn(async move {
|
||||||
|
util::syntax_highlight::highlight_content(new_content, file_type, &theme_syntax)
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = cx
|
||||||
|
.spawn(async move |weak, cx| match tokio::join!(t1, t2) {
|
||||||
|
| (Some(old_side_highlights), Some(new_side_highlights)) => {
|
||||||
|
_ = weak.update(cx, |this, cx| {
|
||||||
|
this.diff_view_state
|
||||||
|
.set_old_side_highlights(old_side_highlights);
|
||||||
|
this.diff_view_state
|
||||||
|
.set_new_side_highlights(new_side_highlights);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
| _ => {}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui::Render for PullRequestDiffView {
|
impl gpui::Render for PullRequestDiffView {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
markdown::{self, MarkdownText},
|
markdown::{self, MarkdownText},
|
||||||
text::text,
|
text::text,
|
||||||
},
|
},
|
||||||
query::{self, QueryStatus, read_query, use_query},
|
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||||
screen::dashboard::pull_request_diff_view::{self, PullRequestDiffView},
|
screen::dashboard::pull_request_diff_view::{self, PullRequestDiffView},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,21 +46,20 @@ impl PullRequestView {
|
|||||||
|
|
||||||
self.pull_request_query = Some(query.clone());
|
self.pull_request_query = Some(query.clone());
|
||||||
|
|
||||||
_ = cx
|
_ = watch_query(&query, Self::sync_pull_request_query, cx).detach();
|
||||||
.observe(&query.clone(), move |this, _, cx| {
|
|
||||||
this.load_markdown_content(cx);
|
|
||||||
this.load_pr_diff(cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
// cached query will not trigger observe callback
|
|
||||||
// this is required so that content is loaded immediately for cached query
|
|
||||||
self.load_markdown_content(cx);
|
|
||||||
self.load_pr_diff(cx);
|
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_pull_request_query(
|
||||||
|
&mut self,
|
||||||
|
_query: &query::Entity<api::issues::FetchPullRequest>,
|
||||||
|
cx: &mut gpui::Context<Self>,
|
||||||
|
) {
|
||||||
|
self.load_markdown_content(cx);
|
||||||
|
self.load_pr_diff(cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn load_markdown_content(&mut self, cx: &mut gpui::Context<Self>) {
|
fn load_markdown_content(&mut self, cx: &mut gpui::Context<Self>) {
|
||||||
let Some(query) = &self.pull_request_query else {
|
let Some(query) = &self.pull_request_query else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use std::{ops::Range, sync::Arc};
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AppContext, InteractiveElement, IntoElement, ParentElement,
|
AnyElement, AppContext, InteractiveElement, IntoElement, ParentElement,
|
||||||
StatefulInteractiveElement,
|
StatefulInteractiveElement, Styled, div, point, px, size,
|
||||||
Styled, div, point, px, size,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -54,6 +53,8 @@ pub(crate) struct Screen {
|
|||||||
struct DiffCase {
|
struct DiffCase {
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
description: &'static str,
|
description: &'static str,
|
||||||
|
old_line_count: usize,
|
||||||
|
new_line_count: usize,
|
||||||
old_lines: Vec<SourceLine>,
|
old_lines: Vec<SourceLine>,
|
||||||
new_lines: Vec<SourceLine>,
|
new_lines: Vec<SourceLine>,
|
||||||
op_groups: Vec<OpGroup>,
|
op_groups: Vec<OpGroup>,
|
||||||
@@ -233,8 +234,8 @@ impl gpui::Render for Screen {
|
|||||||
text(format!(
|
text(format!(
|
||||||
"{} ops, {} old lines, {} new lines",
|
"{} ops, {} old lines, {} new lines",
|
||||||
case.op_groups.len(),
|
case.op_groups.len(),
|
||||||
line_count(&case.old_lines),
|
case.old_line_count,
|
||||||
line_count(&case.new_lines),
|
case.new_line_count,
|
||||||
))
|
))
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.font_family("Menlo")
|
.font_family("Menlo")
|
||||||
@@ -252,11 +253,11 @@ impl gpui::Render for Screen {
|
|||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(theme.colors.border_muted)
|
.border_color(theme.colors.border_muted)
|
||||||
.child(
|
.child(
|
||||||
panel_header("Old", line_count(&case.old_lines), theme)
|
panel_header("Old", case.old_line_count, theme)
|
||||||
.flex_1(),
|
.flex_1(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
panel_header("New", line_count(&case.new_lines), theme)
|
panel_header("New", case.new_line_count, theme)
|
||||||
.flex_1(),
|
.flex_1(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -276,6 +277,8 @@ impl gpui::Render for Screen {
|
|||||||
.child(render_source_content(
|
.child(render_source_content(
|
||||||
&case.old_lines,
|
&case.old_lines,
|
||||||
&case.new_lines,
|
&case.new_lines,
|
||||||
|
case.old_line_count,
|
||||||
|
case.new_line_count,
|
||||||
theme,
|
theme,
|
||||||
))
|
))
|
||||||
.child(text("Diff Rows Render").text_sm())
|
.child(text("Diff Rows Render").text_sm())
|
||||||
@@ -377,6 +380,8 @@ impl DiffCase {
|
|||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
old_line_count: diff.old_line_count,
|
||||||
|
new_line_count: diff.new_line_count,
|
||||||
old_lines: collect_source_lines(&diff, SourceSide::Old),
|
old_lines: collect_source_lines(&diff, SourceSide::Old),
|
||||||
new_lines: collect_source_lines(&diff, SourceSide::New),
|
new_lines: collect_source_lines(&diff, SourceSide::New),
|
||||||
op_groups: collect_op_groups(&diff),
|
op_groups: collect_op_groups(&diff),
|
||||||
@@ -408,23 +413,24 @@ fn panel_header(label: &'static str, line_count: usize, theme: &crate::theme::Th
|
|||||||
fn render_source_content(
|
fn render_source_content(
|
||||||
old_lines: &[SourceLine],
|
old_lines: &[SourceLine],
|
||||||
new_lines: &[SourceLine],
|
new_lines: &[SourceLine],
|
||||||
|
old_line_count: usize,
|
||||||
|
new_line_count: usize,
|
||||||
theme: &crate::theme::Theme,
|
theme: &crate::theme::Theme,
|
||||||
) -> gpui::Div {
|
) -> gpui::Div {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(render_source_panel("Old Content", old_lines, theme).flex_1())
|
.child(render_source_panel("Old Content", old_lines, old_line_count, theme).flex_1())
|
||||||
.child(render_source_panel("New Content", new_lines, theme).flex_1())
|
.child(render_source_panel("New Content", new_lines, new_line_count, theme).flex_1())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_source_panel(
|
fn render_source_panel(
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
lines: &[SourceLine],
|
lines: &[SourceLine],
|
||||||
|
line_count: usize,
|
||||||
theme: &crate::theme::Theme,
|
theme: &crate::theme::Theme,
|
||||||
) -> gpui::Div {
|
) -> gpui::Div {
|
||||||
let line_count = line_count(lines);
|
|
||||||
|
|
||||||
let rows: Vec<AnyElement> = lines
|
let rows: Vec<AnyElement> = lines
|
||||||
.iter()
|
.iter()
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
@@ -573,7 +579,7 @@ fn render_row(op_index: usize, row: &DiffLine, theme: &crate::theme::Theme) -> g
|
|||||||
.child(render_line_cell(
|
.child(render_line_cell(
|
||||||
op_index,
|
op_index,
|
||||||
row.op,
|
row.op,
|
||||||
row.old_content.as_ref().map(|_| row.old_line),
|
row.old_line,
|
||||||
row.old_content.as_deref().map(display_text),
|
row.old_content.as_deref().map(display_text),
|
||||||
true,
|
true,
|
||||||
theme,
|
theme,
|
||||||
@@ -581,7 +587,7 @@ fn render_row(op_index: usize, row: &DiffLine, theme: &crate::theme::Theme) -> g
|
|||||||
.child(render_line_cell(
|
.child(render_line_cell(
|
||||||
op_index,
|
op_index,
|
||||||
row.op,
|
row.op,
|
||||||
row.new_content.as_ref().map(|_| row.new_line),
|
row.new_line,
|
||||||
row.new_content.as_deref().map(display_text),
|
row.new_content.as_deref().map(display_text),
|
||||||
false,
|
false,
|
||||||
theme,
|
theme,
|
||||||
@@ -661,10 +667,6 @@ fn display_text(text: &str) -> String {
|
|||||||
rendered
|
rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_count(lines: &[SourceLine]) -> usize {
|
|
||||||
lines.last().map(|line| line.line_number + 1).unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_source_lines(diff: &ContentDiff, side: SourceSide) -> Vec<SourceLine> {
|
fn collect_source_lines(diff: &ContentDiff, side: SourceSide) -> Vec<SourceLine> {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
@@ -673,17 +675,17 @@ fn collect_source_lines(diff: &ContentDiff, side: SourceSide) -> Vec<SourceLine>
|
|||||||
|
|
||||||
match side {
|
match side {
|
||||||
| SourceSide::Old => {
|
| SourceSide::Old => {
|
||||||
if let Some(content) = &row.old_content {
|
if let (Some(line_number), Some(content)) = (row.old_line, &row.old_content) {
|
||||||
lines.push(SourceLine {
|
lines.push(SourceLine {
|
||||||
line_number: row.old_line,
|
line_number,
|
||||||
content: Arc::clone(content),
|
content: Arc::clone(content),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
| SourceSide::New => {
|
| SourceSide::New => {
|
||||||
if let Some(content) = &row.new_content {
|
if let (Some(line_number), Some(content)) = (row.new_line, &row.new_content) {
|
||||||
lines.push(SourceLine {
|
lines.push(SourceLine {
|
||||||
line_number: row.new_line,
|
line_number,
|
||||||
content: Arc::clone(content),
|
content: Arc::clone(content),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -710,8 +712,8 @@ fn collect_op_groups(diff: &ContentDiff) -> Vec<OpGroup> {
|
|||||||
|
|
||||||
groups.push(OpGroup {
|
groups.push(OpGroup {
|
||||||
op,
|
op,
|
||||||
old_range: group_range(&rows, SourceSide::Old),
|
old_range: group_range(diff, start, end, SourceSide::Old),
|
||||||
new_range: group_range(&rows, SourceSide::New),
|
new_range: group_range(diff, start, end, SourceSide::New),
|
||||||
rows,
|
rows,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -721,18 +723,13 @@ fn collect_op_groups(diff: &ContentDiff) -> Vec<OpGroup> {
|
|||||||
groups
|
groups
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_range(rows: &[DiffLine], side: SourceSide) -> Range<usize> {
|
fn group_range(diff: &ContentDiff, start: usize, end: usize, side: SourceSide) -> Range<usize> {
|
||||||
let anchor = match side {
|
|
||||||
| SourceSide::Old => rows.first().map(|row| row.old_line).unwrap_or(0),
|
|
||||||
| SourceSide::New => rows.first().map(|row| row.new_line).unwrap_or(0),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut first = None;
|
let mut first = None;
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
|
|
||||||
for line_number in rows.iter().filter_map(|row| match side {
|
for line_number in (start..end).filter_map(|index| match side {
|
||||||
| SourceSide::Old => row.old_content.as_ref().map(|_| row.old_line),
|
| SourceSide::Old => diff.get(index).old_line,
|
||||||
| SourceSide::New => row.new_content.as_ref().map(|_| row.new_line),
|
| SourceSide::New => diff.get(index).new_line,
|
||||||
}) {
|
}) {
|
||||||
if first.is_none() {
|
if first.is_none() {
|
||||||
first = Some(line_number);
|
first = Some(line_number);
|
||||||
@@ -742,8 +739,29 @@ fn group_range(rows: &[DiffLine], side: SourceSide) -> Range<usize> {
|
|||||||
|
|
||||||
match (first, last) {
|
match (first, last) {
|
||||||
| (Some(start), Some(end)) => start..end + 1,
|
| (Some(start), Some(end)) => start..end + 1,
|
||||||
| _ => anchor..anchor,
|
| _ => {
|
||||||
|
let anchor = group_anchor(diff, start, end, side);
|
||||||
|
anchor..anchor
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_anchor(diff: &ContentDiff, start: usize, end: usize, side: SourceSide) -> usize {
|
||||||
|
if let Some(line_number) = (end..diff.len()).find_map(|index| match side {
|
||||||
|
| SourceSide::Old => diff.get(index).old_line,
|
||||||
|
| SourceSide::New => diff.get(index).new_line,
|
||||||
|
}) {
|
||||||
|
return line_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(line_number) = (0..start).rev().find_map(|index| match side {
|
||||||
|
| SourceSide::Old => diff.get(index).old_line,
|
||||||
|
| SourceSide::New => diff.get(index).new_line,
|
||||||
|
}) {
|
||||||
|
return line_number + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Colors {
|
struct Colors {
|
||||||
|
|||||||
@@ -356,7 +356,9 @@ impl gpui::Render for GithubStepView {
|
|||||||
| Some(ref q) => {
|
| Some(ref q) => {
|
||||||
let user_query = read_query(q, cx);
|
let user_query = read_query(q, cx);
|
||||||
match user_query {
|
match user_query {
|
||||||
| QueryStatus::Loaded(user) => (true, connected_header(), connected_body(user, cx)),
|
| QueryStatus::Loaded(user) => {
|
||||||
|
(true, connected_header(), connected_body(user, cx))
|
||||||
|
}
|
||||||
| _ => (false, self.header(), self.device_code_area(cx)),
|
| _ => (false, self.header(), self.device_code_area(cx)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
mod catppuccin;
|
mod catppuccin;
|
||||||
|
pub(crate) mod syntax;
|
||||||
|
|
||||||
use gpui::Rgba;
|
use gpui::Rgba;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use syntax::{HIGHLIGHT_NAMES, ThemeSyntax, ThemeSyntaxHighlight};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum ThemeMode {
|
pub enum ThemeMode {
|
||||||
Light,
|
Light,
|
||||||
@@ -15,6 +19,7 @@ pub struct Theme {
|
|||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub mode: ThemeMode,
|
pub mode: ThemeMode,
|
||||||
pub colors: ThemeColors,
|
pub colors: ThemeColors,
|
||||||
|
pub syntax: ThemeSyntax,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Theme {
|
impl Default for Theme {
|
||||||
@@ -103,7 +108,7 @@ impl ThemeFamily {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn theme(self, mode: ThemeMode) -> Theme {
|
pub fn theme(self, mode: ThemeMode) -> Theme {
|
||||||
self.variant(mode).theme()
|
self.variant(mode).theme()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +149,7 @@ impl ThemeVariant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn theme(self) -> Theme {
|
pub fn theme(self) -> Theme {
|
||||||
match self {
|
match self {
|
||||||
| Self::CatppuccinLatte => catppuccin::latte(),
|
| Self::CatppuccinLatte => catppuccin::latte(),
|
||||||
| Self::CatppuccinMocha => catppuccin::mocha(),
|
| Self::CatppuccinMocha => catppuccin::mocha(),
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use crate::colors::{hex, hex_alpha};
|
use crate::colors::{hex, hex_alpha};
|
||||||
|
|
||||||
use super::{Theme, ThemeColors, ThemeMode};
|
use super::{
|
||||||
|
Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight,
|
||||||
|
syntax::{build, key},
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) const FAMILY_ID: &str = "catppuccin";
|
pub(crate) const FAMILY_ID: &str = "catppuccin";
|
||||||
pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
|
pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
|
||||||
@@ -8,7 +11,31 @@ pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
|
|||||||
pub(crate) const LATTE_LABEL: &str = "Catppuccin Latte";
|
pub(crate) const LATTE_LABEL: &str = "Catppuccin Latte";
|
||||||
pub(crate) const MOCHA_LABEL: &str = "Catppuccin Mocha";
|
pub(crate) const MOCHA_LABEL: &str = "Catppuccin Mocha";
|
||||||
|
|
||||||
pub(crate) const fn latte() -> Theme {
|
const fn highlight(color: u32) -> ThemeSyntaxHighlight {
|
||||||
|
ThemeSyntaxHighlight {
|
||||||
|
color: hex(color),
|
||||||
|
font_style: gpui::FontStyle::Normal,
|
||||||
|
font_weight: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn highlight_italic(color: u32) -> ThemeSyntaxHighlight {
|
||||||
|
ThemeSyntaxHighlight {
|
||||||
|
color: hex(color),
|
||||||
|
font_style: gpui::FontStyle::Italic,
|
||||||
|
font_weight: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn highlight_weight(color: u32, font_weight: gpui::FontWeight) -> ThemeSyntaxHighlight {
|
||||||
|
ThemeSyntaxHighlight {
|
||||||
|
color: hex(color),
|
||||||
|
font_style: gpui::FontStyle::Normal,
|
||||||
|
font_weight: Some(font_weight),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn latte() -> Theme {
|
||||||
Theme {
|
Theme {
|
||||||
id: "catppuccin-latte",
|
id: "catppuccin-latte",
|
||||||
name: LATTE_LABEL,
|
name: LATTE_LABEL,
|
||||||
@@ -61,10 +88,63 @@ pub(crate) const fn latte() -> Theme {
|
|||||||
info_solid: hex(0x1e66f5),
|
info_solid: hex(0x1e66f5),
|
||||||
info_on_solid: hex(0xeff1f5),
|
info_on_solid: hex(0xeff1f5),
|
||||||
},
|
},
|
||||||
|
syntax: build([
|
||||||
|
(key::ATTRIBUTE, highlight(0x179299)),
|
||||||
|
(key::BOOLEAN, highlight(0xfe640b)),
|
||||||
|
(key::COMMENT, highlight(0x7c7f93)),
|
||||||
|
(key::COMMENT_DOC, highlight(0x8c8fa1)),
|
||||||
|
(key::CONSTANT, highlight(0xfe640b)),
|
||||||
|
(key::CONSTRUCTOR, highlight(0x209fb5)),
|
||||||
|
(key::EMBEDDED, highlight(0x4c4f69)),
|
||||||
|
(key::EMPHASIS, highlight(0x1e66f5)),
|
||||||
|
(
|
||||||
|
key::EMPHASIS_STRONG,
|
||||||
|
highlight_weight(0xdf8e1d, gpui::FontWeight::BOLD),
|
||||||
|
),
|
||||||
|
(key::ENUM, highlight(0x179299)),
|
||||||
|
(key::FUNCTION, highlight(0x1e66f5)),
|
||||||
|
(key::HINT, highlight(0x9ca0b0)),
|
||||||
|
(key::KEYWORD, highlight(0x8839ef)),
|
||||||
|
(key::LABEL, highlight(0x7287fd)),
|
||||||
|
(key::LINK_TEXT, highlight_italic(0x1e66f5)),
|
||||||
|
(key::LINK_URI, highlight(0x179299)),
|
||||||
|
(key::NAMESPACE, highlight(0x4c4f69)),
|
||||||
|
(key::NUMBER, highlight(0xfe640b)),
|
||||||
|
(key::OPERATOR, highlight(0x04a5e5)),
|
||||||
|
(key::PREDICTIVE, highlight_italic(0x9ca0b0)),
|
||||||
|
(key::PREPROC, highlight(0x8839ef)),
|
||||||
|
(key::PRIMARY, highlight(0x4c4f69)),
|
||||||
|
(key::PROPERTY, highlight(0xdc8a78)),
|
||||||
|
(key::PUNCTUATION, highlight(0x6c6f85)),
|
||||||
|
(key::PUNCTUATION_BRACKET, highlight(0x7c7f93)),
|
||||||
|
(key::PUNCTUATION_DELIMITER, highlight(0x7c7f93)),
|
||||||
|
(key::PUNCTUATION_LIST_MARKER, highlight(0xd20f39)),
|
||||||
|
(key::PUNCTUATION_MARKUP, highlight(0xd20f39)),
|
||||||
|
(key::PUNCTUATION_SPECIAL, highlight(0xe64553)),
|
||||||
|
(key::SELECTOR, highlight(0x40a02b)),
|
||||||
|
(key::SELECTOR_PSEUDO, highlight(0x1e66f5)),
|
||||||
|
(key::STRING, highlight(0x40a02b)),
|
||||||
|
(key::STRING_ESCAPE, highlight(0xea76cb)),
|
||||||
|
(key::STRING_REGEX, highlight(0xfe640b)),
|
||||||
|
(key::STRING_SPECIAL, highlight(0xfe640b)),
|
||||||
|
(key::STRING_SPECIAL_SYMBOL, highlight(0xfe640b)),
|
||||||
|
(key::TAG, highlight(0x1e66f5)),
|
||||||
|
(key::TEXT_LITERAL, highlight(0x40a02b)),
|
||||||
|
(
|
||||||
|
key::TITLE,
|
||||||
|
highlight_weight(0xdc8a78, gpui::FontWeight::NORMAL),
|
||||||
|
),
|
||||||
|
(key::TYPE, highlight(0xdf8e1d)),
|
||||||
|
(key::VARIABLE, highlight(0x4c4f69)),
|
||||||
|
(key::VARIABLE_SPECIAL, highlight(0xfe640b)),
|
||||||
|
(key::VARIANT, highlight(0x7287fd)),
|
||||||
|
(key::DIFF_PLUS, highlight(0x40a02b)),
|
||||||
|
(key::DIFF_MINUS, highlight(0xd20f39)),
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn mocha() -> Theme {
|
pub(crate) fn mocha() -> Theme {
|
||||||
Theme {
|
Theme {
|
||||||
id: "catppuccin-mocha",
|
id: "catppuccin-mocha",
|
||||||
name: MOCHA_LABEL,
|
name: MOCHA_LABEL,
|
||||||
@@ -117,5 +197,58 @@ pub(crate) const fn mocha() -> Theme {
|
|||||||
info_solid: hex(0x89b4fa),
|
info_solid: hex(0x89b4fa),
|
||||||
info_on_solid: hex(0x1e1e2e),
|
info_on_solid: hex(0x1e1e2e),
|
||||||
},
|
},
|
||||||
|
syntax: build([
|
||||||
|
(key::ATTRIBUTE, highlight(0x94e2d5)),
|
||||||
|
(key::BOOLEAN, highlight(0xfab387)),
|
||||||
|
(key::COMMENT, highlight(0x6c7086)),
|
||||||
|
(key::COMMENT_DOC, highlight(0x7f849c)),
|
||||||
|
(key::CONSTANT, highlight(0xfab387)),
|
||||||
|
(key::CONSTRUCTOR, highlight(0x74c7ec)),
|
||||||
|
(key::EMBEDDED, highlight(0xcdd6f4)),
|
||||||
|
(key::EMPHASIS, highlight(0x89b4fa)),
|
||||||
|
(
|
||||||
|
key::EMPHASIS_STRONG,
|
||||||
|
highlight_weight(0xf9e2af, gpui::FontWeight::BOLD),
|
||||||
|
),
|
||||||
|
(key::ENUM, highlight(0x94e2d5)),
|
||||||
|
(key::FUNCTION, highlight(0x89b4fa)),
|
||||||
|
(key::HINT, highlight(0x9399b2)),
|
||||||
|
(key::KEYWORD, highlight(0xcba6f7)),
|
||||||
|
(key::LABEL, highlight(0xb4befe)),
|
||||||
|
(key::LINK_TEXT, highlight_italic(0x89b4fa)),
|
||||||
|
(key::LINK_URI, highlight(0x94e2d5)),
|
||||||
|
(key::NAMESPACE, highlight(0xcdd6f4)),
|
||||||
|
(key::NUMBER, highlight(0xfab387)),
|
||||||
|
(key::OPERATOR, highlight(0x89dceb)),
|
||||||
|
(key::PREDICTIVE, highlight_italic(0x9399b2)),
|
||||||
|
(key::PREPROC, highlight(0xcba6f7)),
|
||||||
|
(key::PRIMARY, highlight(0xcdd6f4)),
|
||||||
|
(key::PROPERTY, highlight(0xf5e0dc)),
|
||||||
|
(key::PUNCTUATION, highlight(0xa6adc8)),
|
||||||
|
(key::PUNCTUATION_BRACKET, highlight(0x9399b2)),
|
||||||
|
(key::PUNCTUATION_DELIMITER, highlight(0x9399b2)),
|
||||||
|
(key::PUNCTUATION_LIST_MARKER, highlight(0xf38ba8)),
|
||||||
|
(key::PUNCTUATION_MARKUP, highlight(0xf38ba8)),
|
||||||
|
(key::PUNCTUATION_SPECIAL, highlight(0xeba0ac)),
|
||||||
|
(key::SELECTOR, highlight(0xa6e3a1)),
|
||||||
|
(key::SELECTOR_PSEUDO, highlight(0x89b4fa)),
|
||||||
|
(key::STRING, highlight(0xa6e3a1)),
|
||||||
|
(key::STRING_ESCAPE, highlight(0xf5c2e7)),
|
||||||
|
(key::STRING_REGEX, highlight(0xfab387)),
|
||||||
|
(key::STRING_SPECIAL, highlight(0xfab387)),
|
||||||
|
(key::STRING_SPECIAL_SYMBOL, highlight(0xfab387)),
|
||||||
|
(key::TAG, highlight(0x89b4fa)),
|
||||||
|
(key::TEXT_LITERAL, highlight(0xa6e3a1)),
|
||||||
|
(
|
||||||
|
key::TITLE,
|
||||||
|
highlight_weight(0xf5e0dc, gpui::FontWeight::NORMAL),
|
||||||
|
),
|
||||||
|
(key::TYPE, highlight(0xf9e2af)),
|
||||||
|
(key::VARIABLE, highlight(0xcdd6f4)),
|
||||||
|
(key::VARIABLE_SPECIAL, highlight(0xfab387)),
|
||||||
|
(key::VARIANT, highlight(0xb4befe)),
|
||||||
|
(key::DIFF_PLUS, highlight(0xa6e3a1)),
|
||||||
|
(key::DIFF_MINUS, highlight(0xf38ba8)),
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
src/theme/syntax.rs
Normal file
165
src/theme/syntax.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use gpui::Rgba;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ThemeSyntaxHighlight {
|
||||||
|
pub color: Rgba,
|
||||||
|
pub font_style: gpui::FontStyle,
|
||||||
|
pub font_weight: Option<gpui::FontWeight>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const HIGHLIGHT_NAME_COUNT: usize = HIGHLIGHT_NAMES.len();
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ThemeSyntax([ThemeSyntaxHighlight; HIGHLIGHT_NAME_COUNT]);
|
||||||
|
|
||||||
|
impl ThemeSyntax {
|
||||||
|
pub fn resolve(&self, key: &str) -> ThemeSyntaxHighlight {
|
||||||
|
Self::index_of(key)
|
||||||
|
.and_then(|index| self.get(index))
|
||||||
|
.unwrap_or_else(|| self.text_literal())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Option<ThemeSyntaxHighlight> {
|
||||||
|
self.0.get(index).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[ThemeSyntaxHighlight] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_literal(&self) -> ThemeSyntaxHighlight {
|
||||||
|
self.get(Self::index_of(key::TEXT_LITERAL).expect("missing text.literal index"))
|
||||||
|
.expect("theme syntax must define text.literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_of(key: &str) -> Option<usize> {
|
||||||
|
HIGHLIGHT_NAMES
|
||||||
|
.iter()
|
||||||
|
.position(|candidate| *candidate == key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const HIGHLIGHT_NAMES: &[&str] = &[
|
||||||
|
key::ATTRIBUTE,
|
||||||
|
key::BOOLEAN,
|
||||||
|
key::COMMENT,
|
||||||
|
key::COMMENT_DOC,
|
||||||
|
key::CONSTANT,
|
||||||
|
key::CONSTRUCTOR,
|
||||||
|
key::DIFF_MINUS,
|
||||||
|
key::DIFF_PLUS,
|
||||||
|
key::EMBEDDED,
|
||||||
|
key::EMPHASIS,
|
||||||
|
key::EMPHASIS_STRONG,
|
||||||
|
key::ENUM,
|
||||||
|
key::FUNCTION,
|
||||||
|
key::HINT,
|
||||||
|
key::KEYWORD,
|
||||||
|
key::LABEL,
|
||||||
|
key::LINK_TEXT,
|
||||||
|
key::LINK_URI,
|
||||||
|
key::NAMESPACE,
|
||||||
|
key::NUMBER,
|
||||||
|
key::OPERATOR,
|
||||||
|
key::PREDICTIVE,
|
||||||
|
key::PREPROC,
|
||||||
|
key::PRIMARY,
|
||||||
|
key::PROPERTY,
|
||||||
|
key::PUNCTUATION,
|
||||||
|
key::PUNCTUATION_BRACKET,
|
||||||
|
key::PUNCTUATION_DELIMITER,
|
||||||
|
key::PUNCTUATION_LIST_MARKER,
|
||||||
|
key::PUNCTUATION_MARKUP,
|
||||||
|
key::PUNCTUATION_SPECIAL,
|
||||||
|
key::SELECTOR,
|
||||||
|
key::SELECTOR_PSEUDO,
|
||||||
|
key::STRING,
|
||||||
|
key::STRING_ESCAPE,
|
||||||
|
key::STRING_REGEX,
|
||||||
|
key::STRING_SPECIAL,
|
||||||
|
key::STRING_SPECIAL_SYMBOL,
|
||||||
|
key::TAG,
|
||||||
|
key::TEXT_LITERAL,
|
||||||
|
key::TITLE,
|
||||||
|
key::TYPE,
|
||||||
|
key::VARIABLE,
|
||||||
|
key::VARIABLE_SPECIAL,
|
||||||
|
key::VARIANT,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(crate) fn build(
|
||||||
|
entries: impl IntoIterator<Item = (&'static str, ThemeSyntaxHighlight)>,
|
||||||
|
) -> ThemeSyntax {
|
||||||
|
let syntax_by_name: BTreeMap<&'static str, ThemeSyntaxHighlight> = entries
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, style)| (name, style))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
debug_assert!(syntax_by_name.contains_key(key::TEXT_LITERAL));
|
||||||
|
debug_assert!(
|
||||||
|
syntax_by_name
|
||||||
|
.keys()
|
||||||
|
.all(|name| HIGHLIGHT_NAMES.contains(name))
|
||||||
|
);
|
||||||
|
|
||||||
|
let fallback = *syntax_by_name
|
||||||
|
.get(key::TEXT_LITERAL)
|
||||||
|
.expect("theme syntax must define text.literal");
|
||||||
|
|
||||||
|
ThemeSyntax(std::array::from_fn(|index| {
|
||||||
|
syntax_by_name
|
||||||
|
.get(HIGHLIGHT_NAMES[index])
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(fallback)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod key {
|
||||||
|
pub const ATTRIBUTE: &str = "attribute";
|
||||||
|
pub const BOOLEAN: &str = "boolean";
|
||||||
|
pub const COMMENT: &str = "comment";
|
||||||
|
pub const COMMENT_DOC: &str = "comment.doc";
|
||||||
|
pub const CONSTANT: &str = "constant";
|
||||||
|
pub const CONSTRUCTOR: &str = "constructor";
|
||||||
|
pub const DIFF_MINUS: &str = "diff.minus";
|
||||||
|
pub const DIFF_PLUS: &str = "diff.plus";
|
||||||
|
pub const EMBEDDED: &str = "embedded";
|
||||||
|
pub const EMPHASIS: &str = "emphasis";
|
||||||
|
pub const EMPHASIS_STRONG: &str = "emphasis.strong";
|
||||||
|
pub const ENUM: &str = "enum";
|
||||||
|
pub const FUNCTION: &str = "function";
|
||||||
|
pub const HINT: &str = "hint";
|
||||||
|
pub const KEYWORD: &str = "keyword";
|
||||||
|
pub const LABEL: &str = "label";
|
||||||
|
pub const LINK_TEXT: &str = "link_text";
|
||||||
|
pub const LINK_URI: &str = "link_uri";
|
||||||
|
pub const NAMESPACE: &str = "namespace";
|
||||||
|
pub const NUMBER: &str = "number";
|
||||||
|
pub const OPERATOR: &str = "operator";
|
||||||
|
pub const PREDICTIVE: &str = "predictive";
|
||||||
|
pub const PREPROC: &str = "preproc";
|
||||||
|
pub const PRIMARY: &str = "primary";
|
||||||
|
pub const PROPERTY: &str = "property";
|
||||||
|
pub const PUNCTUATION: &str = "punctuation";
|
||||||
|
pub const PUNCTUATION_BRACKET: &str = "punctuation.bracket";
|
||||||
|
pub const PUNCTUATION_DELIMITER: &str = "punctuation.delimiter";
|
||||||
|
pub const PUNCTUATION_LIST_MARKER: &str = "punctuation.list_marker";
|
||||||
|
pub const PUNCTUATION_MARKUP: &str = "punctuation.markup";
|
||||||
|
pub const PUNCTUATION_SPECIAL: &str = "punctuation.special";
|
||||||
|
pub const SELECTOR: &str = "selector";
|
||||||
|
pub const SELECTOR_PSEUDO: &str = "selector.pseudo";
|
||||||
|
pub const STRING: &str = "string";
|
||||||
|
pub const STRING_ESCAPE: &str = "string.escape";
|
||||||
|
pub const STRING_REGEX: &str = "string.regex";
|
||||||
|
pub const STRING_SPECIAL: &str = "string.special";
|
||||||
|
pub const STRING_SPECIAL_SYMBOL: &str = "string.special.symbol";
|
||||||
|
pub const TAG: &str = "tag";
|
||||||
|
pub const TEXT_LITERAL: &str = "text.literal";
|
||||||
|
pub const TITLE: &str = "title";
|
||||||
|
pub const TYPE: &str = "type";
|
||||||
|
pub const VARIABLE: &str = "variable";
|
||||||
|
pub const VARIABLE_SPECIAL: &str = "variable.special";
|
||||||
|
pub const VARIANT: &str = "variant";
|
||||||
|
}
|
||||||
136
src/util/diff.rs
136
src/util/diff.rs
@@ -1,13 +1,8 @@
|
|||||||
use std::{ops::Range, slice::Iter, sync::Arc, thread::current};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use memchr::{memchr2, memchr2_iter, memchr3_iter};
|
|
||||||
use similar::DiffableStr;
|
use similar::DiffableStr;
|
||||||
|
|
||||||
pub(crate) struct Span {
|
use crate::util;
|
||||||
pub(crate) op: Op,
|
|
||||||
pub(crate) old_range: Range<usize>,
|
|
||||||
pub(crate) new_range: Range<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum Op {
|
pub(crate) enum Op {
|
||||||
@@ -17,39 +12,32 @@ pub(crate) enum Op {
|
|||||||
Replace,
|
Replace,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(crate) enum DiffSide {
|
|
||||||
Old,
|
|
||||||
New,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct DiffRow {
|
|
||||||
pub(crate) op_index: usize,
|
|
||||||
pub(crate) op: Op,
|
|
||||||
pub(crate) old_line: Option<usize>,
|
|
||||||
pub(crate) old_content_range: Option<Range<usize>>,
|
|
||||||
pub(crate) new_line: Option<usize>,
|
|
||||||
pub(crate) new_content_range: Option<Range<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DiffLine {
|
pub(crate) struct DiffLine {
|
||||||
pub(crate) op: Op,
|
pub(crate) op: Op,
|
||||||
pub(crate) old_content: Option<Arc<str>>,
|
pub(crate) old_content: Option<Arc<str>>,
|
||||||
pub(crate) old_line: usize,
|
pub(crate) old_line: Option<usize>,
|
||||||
|
pub(crate) old_byte_range: std::ops::Range<usize>,
|
||||||
pub(crate) new_content: Option<Arc<str>>,
|
pub(crate) new_content: Option<Arc<str>>,
|
||||||
pub(crate) new_line: usize,
|
pub(crate) new_line: Option<usize>,
|
||||||
|
pub(crate) new_byte_range: std::ops::Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ContentDiff(Vec<DiffLine>);
|
pub(crate) struct ContentDiff {
|
||||||
|
pub(crate) diff_lines: Vec<DiffLine>,
|
||||||
|
pub(crate) old_content: bytes::Bytes,
|
||||||
|
pub(crate) old_line_count: usize,
|
||||||
|
pub(crate) new_content: bytes::Bytes,
|
||||||
|
pub(crate) new_line_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn diff_content(
|
pub(crate) fn diff_content(
|
||||||
old_content: bytes::Bytes,
|
old_content: bytes::Bytes,
|
||||||
new_content: bytes::Bytes,
|
new_content: bytes::Bytes,
|
||||||
) -> Option<ContentDiff> {
|
) -> Option<ContentDiff> {
|
||||||
let old_line_ranges = line_ranges(&old_content);
|
let old_line_ranges = util::file::line_ranges(&old_content);
|
||||||
let new_line_ranges = line_ranges(&new_content);
|
let new_line_ranges = util::file::line_ranges(&new_content);
|
||||||
let diff = similar::TextDiff::from_lines::<[u8]>(&old_content, &new_content);
|
let diff = similar::TextDiff::from_lines::<[u8]>(&old_content, &new_content);
|
||||||
|
|
||||||
let mut diff_lines: Vec<DiffLine> = Vec::new();
|
let mut diff_lines: Vec<DiffLine> = Vec::new();
|
||||||
@@ -68,28 +56,30 @@ pub(crate) fn diff_content(
|
|||||||
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?);
|
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?);
|
||||||
diff_lines.push(DiffLine {
|
diff_lines.push(DiffLine {
|
||||||
op: Op::Equal,
|
op: Op::Equal,
|
||||||
old_line,
|
old_line: Some(old_line),
|
||||||
old_content: Some(Arc::clone(&content)),
|
old_content: Some(Arc::clone(&content)),
|
||||||
new_line,
|
old_byte_range: old_line_range.clone(),
|
||||||
|
new_line: Some(new_line),
|
||||||
new_content: Some(content),
|
new_content: Some(content),
|
||||||
|
new_byte_range: new_line_ranges[new_line].clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
| &similar::DiffOp::Insert {
|
| &similar::DiffOp::Insert {
|
||||||
old_index,
|
new_index, new_len, ..
|
||||||
new_index,
|
|
||||||
new_len,
|
|
||||||
} => {
|
} => {
|
||||||
for i in 0..new_len {
|
for i in 0..new_len {
|
||||||
let new_line_range = &new_line_ranges[new_index + i];
|
let new_line_range = &new_line_ranges[new_index + i];
|
||||||
let content = Arc::from(new_content.slice(new_line_range.clone()).as_str()?);
|
let content = Arc::from(new_content.slice(new_line_range.clone()).as_str()?);
|
||||||
diff_lines.push(DiffLine {
|
diff_lines.push(DiffLine {
|
||||||
op: Op::Insert,
|
op: Op::Insert,
|
||||||
old_line: old_index,
|
old_line: None,
|
||||||
old_content: None,
|
old_content: None,
|
||||||
new_line: new_index + i,
|
old_byte_range: 0..0,
|
||||||
|
new_line: Some(new_index + i),
|
||||||
new_content: Some(content),
|
new_content: Some(content),
|
||||||
|
new_byte_range: new_line_range.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,26 +98,32 @@ pub(crate) fn diff_content(
|
|||||||
{
|
{
|
||||||
| (Some(old_range), Some(new_range)) => DiffLine {
|
| (Some(old_range), Some(new_range)) => DiffLine {
|
||||||
op: Op::Replace,
|
op: Op::Replace,
|
||||||
old_line,
|
old_line: Some(old_line),
|
||||||
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
||||||
new_line: new_index + i,
|
old_byte_range: old_range.clone(),
|
||||||
|
new_line: Some(new_line),
|
||||||
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
||||||
|
new_byte_range: new_range.clone(),
|
||||||
},
|
},
|
||||||
|
|
||||||
| (None, Some(new_range)) => DiffLine {
|
| (None, Some(new_range)) => DiffLine {
|
||||||
op: Op::Replace,
|
op: Op::Replace,
|
||||||
old_line: old_index + old_len,
|
old_line: None,
|
||||||
old_content: None,
|
old_content: None,
|
||||||
new_line: new_index + i,
|
old_byte_range: 0..0,
|
||||||
|
new_line: Some(new_index + i),
|
||||||
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
||||||
|
new_byte_range: new_range.clone(),
|
||||||
},
|
},
|
||||||
|
|
||||||
| (Some(old_range), None) => DiffLine {
|
| (Some(old_range), None) => DiffLine {
|
||||||
op: Op::Replace,
|
op: Op::Replace,
|
||||||
old_line: old_index + i,
|
old_line: Some(old_index + i),
|
||||||
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
||||||
new_line: new_index + new_len,
|
old_byte_range: old_range.clone(),
|
||||||
|
new_line: None,
|
||||||
new_content: None,
|
new_content: None,
|
||||||
|
new_byte_range: 0..0,
|
||||||
},
|
},
|
||||||
|
|
||||||
| (None, None) => {
|
| (None, None) => {
|
||||||
@@ -143,74 +139,44 @@ pub(crate) fn diff_content(
|
|||||||
}
|
}
|
||||||
|
|
||||||
| &similar::DiffOp::Delete {
|
| &similar::DiffOp::Delete {
|
||||||
old_index,
|
old_index, old_len, ..
|
||||||
old_len,
|
|
||||||
new_index,
|
|
||||||
} => {
|
} => {
|
||||||
for i in 0..old_len {
|
for i in 0..old_len {
|
||||||
let old_line_range = &old_line_ranges[old_index];
|
let old_line_range = &old_line_ranges[old_index];
|
||||||
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?);
|
let content = Arc::from(old_content.slice(old_line_range.clone()).as_str()?);
|
||||||
diff_lines.push(DiffLine {
|
diff_lines.push(DiffLine {
|
||||||
op: Op::Delete,
|
op: Op::Delete,
|
||||||
old_line: old_index + i,
|
old_line: Some(old_index + i),
|
||||||
old_content: Some(content),
|
old_content: Some(content),
|
||||||
new_line: new_index,
|
old_byte_range: old_line_range.clone(),
|
||||||
|
new_line: None,
|
||||||
new_content: None,
|
new_content: None,
|
||||||
|
new_byte_range: 0..0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ContentDiff(diff_lines))
|
Some(ContentDiff {
|
||||||
|
diff_lines,
|
||||||
|
old_content,
|
||||||
|
old_line_count: old_line_ranges.len(),
|
||||||
|
new_content,
|
||||||
|
new_line_count: new_line_ranges.len(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentDiff {
|
impl ContentDiff {
|
||||||
pub(crate) fn len(&self) -> usize {
|
pub(crate) fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.diff_lines.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get(&self, i: usize) -> &DiffLine {
|
pub(crate) fn get(&self, i: usize) -> &DiffLine {
|
||||||
&self.0[i]
|
&self.diff_lines[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn last(&self) -> Option<&DiffLine> {
|
pub(crate) fn last(&self) -> Option<&DiffLine> {
|
||||||
self.0.last()
|
self.diff_lines.last()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_ranges(content: &[u8]) -> Vec<Range<usize>> {
|
|
||||||
let mut ranges: Vec<std::ops::Range<usize>> = Vec::new();
|
|
||||||
let mut line_start: usize = 0;
|
|
||||||
let mut skip_next = false;
|
|
||||||
|
|
||||||
for i in memchr2_iter(b'\n', b'\r', content) {
|
|
||||||
if skip_next {
|
|
||||||
skip_next = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = content[i];
|
|
||||||
|
|
||||||
match (c, content.get(i + 1)) {
|
|
||||||
| (b'\r', Some(b'\n')) => {
|
|
||||||
// if \r found, check if its \r\n or if its a lone \r
|
|
||||||
// if \r\n, then treat as one line break
|
|
||||||
ranges.push(line_start..i + 1);
|
|
||||||
// because we already counted the \n byte, the next iter into it needs to be skipped
|
|
||||||
skip_next = true;
|
|
||||||
line_start = i + 2;
|
|
||||||
}
|
|
||||||
| _ => {
|
|
||||||
ranges.push(line_start..i);
|
|
||||||
line_start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_start < content.len() {
|
|
||||||
ranges.push(line_start..content.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
use memchr::memchr;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use memchr::{memchr, memchr2_iter};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum ContentType {
|
pub(crate) enum ContentType {
|
||||||
Text,
|
Text,
|
||||||
Binary,
|
Binary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub(crate) enum FileType {
|
||||||
|
Rust,
|
||||||
|
JavaScript,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn classify_content(content: &[u8]) -> ContentType {
|
pub(crate) fn classify_content(content: &[u8]) -> ContentType {
|
||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
ContentType::Text
|
ContentType::Text
|
||||||
@@ -22,3 +32,47 @@ pub(crate) fn classify_content(content: &[u8]) -> ContentType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn file_type_from_path(path: &str) -> FileType {
|
||||||
|
match Path::new(path).extension().map(|it| it.to_str()).flatten() {
|
||||||
|
| Some("rs") => FileType::Rust,
|
||||||
|
| Some("js") | Some("jsx") => FileType::JavaScript,
|
||||||
|
| _ => FileType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn line_ranges(content: &[u8]) -> Vec<std::ops::Range<usize>> {
|
||||||
|
let mut ranges: Vec<std::ops::Range<usize>> = Vec::new();
|
||||||
|
let mut line_start: usize = 0;
|
||||||
|
let mut skip_next = false;
|
||||||
|
|
||||||
|
for i in memchr2_iter(b'\n', b'\r', content) {
|
||||||
|
if skip_next {
|
||||||
|
skip_next = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = content[i];
|
||||||
|
|
||||||
|
match (c, content.get(i + 1)) {
|
||||||
|
| (b'\r', Some(b'\n')) => {
|
||||||
|
// if \r found, check if its \r\n or if its a lone \r
|
||||||
|
// if \r\n, then treat as one line break
|
||||||
|
ranges.push(line_start..i + 1);
|
||||||
|
// because we already counted the \n byte, the next iter into it needs to be skipped
|
||||||
|
skip_next = true;
|
||||||
|
line_start = i + 2;
|
||||||
|
}
|
||||||
|
| _ => {
|
||||||
|
ranges.push(line_start..i);
|
||||||
|
line_start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if line_start < content.len() {
|
||||||
|
ranges.push(line_start..content.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub(crate) mod diff;
|
pub(crate) mod diff;
|
||||||
pub(crate) mod file;
|
pub(crate) mod file;
|
||||||
pub(crate) mod str;
|
pub(crate) mod str;
|
||||||
|
pub(crate) mod syntax_highlight;
|
||||||
pub(crate) mod timeout;
|
pub(crate) mod timeout;
|
||||||
|
|||||||
91
src/util/syntax_highlight.rs
Normal file
91
src/util/syntax_highlight.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use crate::{theme, util};
|
||||||
|
|
||||||
|
pub(crate) struct HighlightedContent(Vec<Vec<(std::ops::Range<usize>, gpui::HighlightStyle)>>);
|
||||||
|
|
||||||
|
fn ts_highlight_configuration_for_file_type(
|
||||||
|
file_type: util::file::FileType,
|
||||||
|
) -> Option<tree_sitter_highlight::HighlightConfiguration> {
|
||||||
|
match file_type {
|
||||||
|
| util::file::FileType::Rust => tree_sitter_highlight::HighlightConfiguration::new(
|
||||||
|
tree_sitter_rust::LANGUAGE.into(),
|
||||||
|
"rust",
|
||||||
|
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||||
|
tree_sitter_rust::INJECTIONS_QUERY,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.ok(),
|
||||||
|
|
||||||
|
| _ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn highlight_content(
|
||||||
|
content: impl AsRef<[u8]>,
|
||||||
|
file_type: util::file::FileType,
|
||||||
|
theme_syntax: &theme::ThemeSyntax,
|
||||||
|
) -> Option<HighlightedContent> {
|
||||||
|
let mut config = ts_highlight_configuration_for_file_type(file_type)?;
|
||||||
|
|
||||||
|
config.configure(theme::syntax::HIGHLIGHT_NAMES);
|
||||||
|
|
||||||
|
let mut highlighter = tree_sitter_highlight::Highlighter::new();
|
||||||
|
let events = highlighter
|
||||||
|
.highlight(&config, content.as_ref(), None, |_| None)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let default_highlight = theme_syntax.resolve(theme::syntax::key::TEXT_LITERAL);
|
||||||
|
let mut highlight = default_highlight;
|
||||||
|
|
||||||
|
let line_ranges = util::file::line_ranges(content.as_ref());
|
||||||
|
|
||||||
|
let mut highlights: Vec<Vec<(std::ops::Range<usize>, gpui::HighlightStyle)>> =
|
||||||
|
Vec::with_capacity(line_ranges.len());
|
||||||
|
let mut current_line: usize = 0;
|
||||||
|
|
||||||
|
for highlight_event in events {
|
||||||
|
match highlight_event.ok()? {
|
||||||
|
| tree_sitter_highlight::HighlightEvent::HighlightStart(h) => {
|
||||||
|
highlight = theme_syntax.as_slice()[h.0];
|
||||||
|
}
|
||||||
|
| tree_sitter_highlight::HighlightEvent::Source { start, end } => {
|
||||||
|
while current_line < line_ranges.len() && start >= line_ranges[current_line].end {
|
||||||
|
highlights.push(Vec::new());
|
||||||
|
current_line += 1;
|
||||||
|
}
|
||||||
|
let mut line = current_line;
|
||||||
|
while line < line_ranges.len() && end > line_ranges[line].start {
|
||||||
|
if highlights.get(line).is_none() {
|
||||||
|
highlights.push(Vec::new());
|
||||||
|
}
|
||||||
|
let line_range = &line_ranges[line];
|
||||||
|
let highlight_start = start.max(line_range.start);
|
||||||
|
let highlight_end = end.min(line_range.end);
|
||||||
|
highlights[line].push((
|
||||||
|
(highlight_start - line_range.start)..(highlight_end - line_range.start),
|
||||||
|
gpui::HighlightStyle {
|
||||||
|
color: Some(highlight.color.into()),
|
||||||
|
font_weight: highlight.font_weight,
|
||||||
|
font_style: Some(highlight.font_style),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
line += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| tree_sitter_highlight::HighlightEvent::HighlightEnd => {
|
||||||
|
highlight = default_highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(HighlightedContent(highlights))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HighlightedContent {
|
||||||
|
pub(crate) fn highlights_at_line(
|
||||||
|
&self,
|
||||||
|
line: usize,
|
||||||
|
) -> &Vec<(std::ops::Range<usize>, gpui::HighlightStyle)> {
|
||||||
|
&self.0[line]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user