mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 20:01:33 +00:00
parent
84affa96ff
commit
1f611a9c90
10 changed files with 184 additions and 44 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -8044,6 +8044,7 @@ dependencies = [
|
|||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"markdown",
|
||||
"menu",
|
||||
"ordered-float 2.10.0",
|
||||
"picker",
|
||||
|
@ -8051,9 +8052,7 @@ dependencies = [
|
|||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"theme",
|
||||
"ui",
|
||||
"ui_text_field",
|
||||
"util",
|
||||
|
|
|
@ -191,6 +191,12 @@
|
|||
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Markdown",
|
||||
"bindings": {
|
||||
"ctrl-c": "markdown::Copy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
|
|
|
@ -207,6 +207,12 @@
|
|||
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Markdown",
|
||||
"bindings": {
|
||||
"cmd-c": "markdown::Copy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel", // Used in the assistant crate, which we're replacing
|
||||
"bindings": {
|
||||
|
|
|
@ -440,7 +440,7 @@ impl AssistantChat {
|
|||
Markdown::new(
|
||||
text,
|
||||
self.markdown_style.clone(),
|
||||
self.language_registry.clone(),
|
||||
Some(self.language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -573,7 +573,7 @@ impl AssistantChat {
|
|||
Markdown::new(
|
||||
"".into(),
|
||||
this.markdown_style.clone(),
|
||||
this.language_registry.clone(),
|
||||
Some(this.language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -667,7 +667,7 @@ impl AssistantChat {
|
|||
Markdown::new(
|
||||
"".into(),
|
||||
self.markdown_style.clone(),
|
||||
self.language_registry.clone(),
|
||||
Some(self.language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
@ -683,7 +683,7 @@ impl AssistantChat {
|
|||
Markdown::new(
|
||||
"".into(),
|
||||
self.markdown_style.clone(),
|
||||
self.language_registry.clone(),
|
||||
Some(self.language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
|
|
|
@ -432,6 +432,19 @@ impl TextLayout {
|
|||
pub fn line_height(&self) -> Pixels {
|
||||
self.0.lock().as_ref().unwrap().line_height
|
||||
}
|
||||
|
||||
/// todo!()
|
||||
pub fn text(&self) -> String {
|
||||
self.0
|
||||
.lock()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lines
|
||||
.iter()
|
||||
.map(|s| s.text.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/// A text element that can be interacted with.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use assets::Assets;
|
||||
use gpui::{prelude::*, App, Task, View, WindowOptions};
|
||||
use gpui::{prelude::*, App, KeyBinding, Task, View, WindowOptions};
|
||||
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
|
@ -91,6 +91,7 @@ pub fn main() {
|
|||
SettingsStore::update(cx, |store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
||||
});
|
||||
cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
|
||||
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
let language_registry = Arc::new(LanguageRegistry::new(
|
||||
|
@ -161,7 +162,7 @@ impl MarkdownExample {
|
|||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
let markdown = cx.new_view(|cx| Markdown::new(text, style, language_registry, cx));
|
||||
let markdown = cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx));
|
||||
Self { markdown }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ mod parser;
|
|||
use crate::parser::CodeBlockKind;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
point, quad, AnyElement, AppContext, Bounds, CursorStyle, DispatchPhase, Edges, FocusHandle,
|
||||
FocusableView, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, KeyContext,
|
||||
MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point, Render, StrikethroughStyle,
|
||||
Style, StyledText, Task, TextLayout, TextRun, TextStyle, TextStyleRefinement, View,
|
||||
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
|
||||
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
|
||||
Hitbox, Hsla, KeyContext, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent, Point,
|
||||
Render, StrikethroughStyle, Style, StyledText, Task, TextLayout, TextRun, TextStyle,
|
||||
TextStyleRefinement, View,
|
||||
};
|
||||
use language::{Language, LanguageRegistry, Rope};
|
||||
use parser::{parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
|
||||
|
@ -37,14 +38,16 @@ pub struct Markdown {
|
|||
should_reparse: bool,
|
||||
pending_parse: Option<Task<Option<()>>>,
|
||||
focus_handle: FocusHandle,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
}
|
||||
|
||||
actions!(markdown, [Copy]);
|
||||
|
||||
impl Markdown {
|
||||
pub fn new(
|
||||
source: String,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
@ -83,6 +86,11 @@ impl Markdown {
|
|||
&self.source
|
||||
}
|
||||
|
||||
fn copy(&self, text: &RenderedText, cx: &mut ViewContext<Self>) {
|
||||
let text = text.text_for_range(self.selection.start..self.selection.end);
|
||||
cx.write_to_clipboard(ClipboardItem::new(text));
|
||||
}
|
||||
|
||||
fn parse(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.source.is_empty() {
|
||||
return;
|
||||
|
@ -191,14 +199,14 @@ impl Default for ParsedMarkdown {
|
|||
pub struct MarkdownElement {
|
||||
markdown: View<Markdown>,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
}
|
||||
|
||||
impl MarkdownElement {
|
||||
fn new(
|
||||
markdown: View<Markdown>,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
markdown,
|
||||
|
@ -210,6 +218,7 @@ impl MarkdownElement {
|
|||
fn load_language(&self, name: &str, cx: &mut WindowContext) -> Option<Arc<Language>> {
|
||||
let language = self
|
||||
.language_registry
|
||||
.as_ref()?
|
||||
.language_for_name(name)
|
||||
.map(|language| language.ok())
|
||||
.shared();
|
||||
|
@ -322,13 +331,21 @@ impl MarkdownElement {
|
|||
match rendered_text.source_index_for_position(event.position) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
let range = if event.click_count == 2 {
|
||||
rendered_text.surrounding_word_range(source_index)
|
||||
} else if event.click_count == 3 {
|
||||
rendered_text.surrounding_line_range(source_index)
|
||||
} else {
|
||||
source_index..source_index
|
||||
};
|
||||
markdown.selection = Selection {
|
||||
start: source_index,
|
||||
end: source_index,
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
reversed: false,
|
||||
pending: true,
|
||||
};
|
||||
cx.focus(&markdown.focus_handle);
|
||||
cx.prevent_default()
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
|
@ -378,6 +395,12 @@ impl MarkdownElement {
|
|||
} else {
|
||||
if markdown.selection.pending {
|
||||
markdown.selection.pending = false;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let text = rendered_text
|
||||
.text_for_range(markdown.selection.start..markdown.selection.end);
|
||||
cx.write_to_primary(ClipboardItem::new(text))
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
@ -619,6 +642,16 @@ impl Element for MarkdownElement {
|
|||
let mut context = KeyContext::default();
|
||||
context.add("Markdown");
|
||||
cx.set_key_context(context);
|
||||
let view = self.markdown.clone();
|
||||
cx.on_action(std::any::TypeId::of::<crate::Copy>(), {
|
||||
let text = rendered_markdown.text.clone();
|
||||
move |_, phase, cx| {
|
||||
let text = text.clone();
|
||||
if phase == DispatchPhase::Bubble {
|
||||
view.update(cx, move |this, cx| this.copy(&text, cx))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.paint_mouse_listeners(hitbox, &rendered_markdown.text, cx);
|
||||
rendered_markdown.element.paint(cx);
|
||||
|
@ -920,6 +953,77 @@ impl RenderedText {
|
|||
None
|
||||
}
|
||||
|
||||
fn surrounding_word_range(&self, source_index: usize) -> Range<usize> {
|
||||
for line in self.lines.iter() {
|
||||
if source_index > line.source_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let line_rendered_start = line.source_mappings.first().unwrap().rendered_index;
|
||||
let rendered_index_in_line =
|
||||
line.rendered_index_for_source_index(source_index) - line_rendered_start;
|
||||
let text = line.layout.text();
|
||||
let previous_space = if let Some(idx) = text[0..rendered_index_in_line].rfind(' ') {
|
||||
idx + ' '.len_utf8()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let next_space = if let Some(idx) = text[rendered_index_in_line..].find(' ') {
|
||||
rendered_index_in_line + idx
|
||||
} else {
|
||||
text.len()
|
||||
};
|
||||
|
||||
return line.source_index_for_rendered_index(line_rendered_start + previous_space)
|
||||
..line.source_index_for_rendered_index(line_rendered_start + next_space);
|
||||
}
|
||||
|
||||
source_index..source_index
|
||||
}
|
||||
|
||||
fn surrounding_line_range(&self, source_index: usize) -> Range<usize> {
|
||||
for line in self.lines.iter() {
|
||||
if source_index > line.source_end {
|
||||
continue;
|
||||
}
|
||||
let line_source_start = line.source_mappings.first().unwrap().source_index;
|
||||
return line_source_start..line.source_end;
|
||||
}
|
||||
|
||||
source_index..source_index
|
||||
}
|
||||
|
||||
fn text_for_range(&self, range: Range<usize>) -> String {
|
||||
let mut ret = vec![];
|
||||
|
||||
for line in self.lines.iter() {
|
||||
if range.start > line.source_end {
|
||||
continue;
|
||||
}
|
||||
let line_source_start = line.source_mappings.first().unwrap().source_index;
|
||||
if range.end < line_source_start {
|
||||
break;
|
||||
}
|
||||
|
||||
let text = line.layout.text();
|
||||
|
||||
let start = if range.start < line_source_start {
|
||||
0
|
||||
} else {
|
||||
line.rendered_index_for_source_index(range.start)
|
||||
};
|
||||
let end = if range.end > line.source_end {
|
||||
line.rendered_index_for_source_index(line.source_end)
|
||||
} else {
|
||||
line.rendered_index_for_source_index(range.end)
|
||||
}
|
||||
.min(text.len());
|
||||
|
||||
ret.push(text[start..end].to_string());
|
||||
}
|
||||
ret.join("\n")
|
||||
}
|
||||
|
||||
fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {
|
||||
let source_index = self.source_index_for_position(position).ok()?;
|
||||
self.links
|
||||
|
|
|
@ -102,7 +102,7 @@ pub struct EntryDetails {
|
|||
is_processing: bool,
|
||||
is_cut: bool,
|
||||
git_status: Option<GitFileStatus>,
|
||||
is_dotenv: bool,
|
||||
is_private: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize)]
|
||||
|
@ -1592,7 +1592,7 @@ impl ProjectPanel {
|
|||
.clipboard_entry
|
||||
.map_or(false, |e| e.is_cut() && e.entry_id() == entry.id),
|
||||
git_status: status,
|
||||
is_dotenv: entry.is_private,
|
||||
is_private: entry.is_private,
|
||||
};
|
||||
|
||||
if let Some(edit_state) = &self.edit_state {
|
||||
|
|
|
@ -18,15 +18,14 @@ editor.workspace = true
|
|||
feature_flags.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
markdown.workspace = true
|
||||
menu.workspace = true
|
||||
ordered-float.workspace = true
|
||||
picker.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
ui_text_field.workspace = true
|
||||
util.workspace = true
|
||||
|
|
|
@ -10,12 +10,12 @@ use gpui::{
|
|||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation,
|
||||
View, ViewContext,
|
||||
};
|
||||
use markdown::Markdown;
|
||||
use markdown::MarkdownStyle;
|
||||
use rpc::{
|
||||
proto::{CreateDevServerResponse, DevServerStatus, RegenerateDevServerTokenResponse},
|
||||
ErrorCode, ErrorExt,
|
||||
};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::CheckboxWithLabel;
|
||||
use ui::{prelude::*, Indicator, List, ListHeader, ListItem, ModalContent, ModalHeader, Tooltip};
|
||||
use ui_text_field::{FieldLabelLayout, TextField};
|
||||
|
@ -33,6 +33,7 @@ pub struct DevServerProjects {
|
|||
dev_server_name_input: View<TextField>,
|
||||
use_server_name_in_ssh: Selection,
|
||||
rename_dev_server_input: View<TextField>,
|
||||
markdown: View<Markdown>,
|
||||
_dev_server_subscription: Subscription,
|
||||
}
|
||||
|
||||
|
@ -113,6 +114,23 @@ impl DevServerProjects {
|
|||
cx.notify();
|
||||
});
|
||||
|
||||
let markdown_style = MarkdownStyle {
|
||||
code_block: gpui::TextStyleRefinement {
|
||||
font_family: Some("Zed Mono".into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
background_color: Some(cx.theme().colors().editor_background),
|
||||
..Default::default()
|
||||
},
|
||||
inline_code: Default::default(),
|
||||
block_quote: Default::default(),
|
||||
link: Default::default(),
|
||||
rule_color: Default::default(),
|
||||
block_quote_border_color: Default::default(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
};
|
||||
let markdown = cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx));
|
||||
|
||||
Self {
|
||||
mode: Mode::Default(None),
|
||||
focus_handle,
|
||||
|
@ -121,6 +139,7 @@ impl DevServerProjects {
|
|||
project_path_input,
|
||||
dev_server_name_input,
|
||||
rename_dev_server_input,
|
||||
markdown,
|
||||
use_server_name_in_ssh: Selection::Unselected,
|
||||
_dev_server_subscription: subscription,
|
||||
}
|
||||
|
@ -726,7 +745,7 @@ impl DevServerProjects {
|
|||
.child(
|
||||
CheckboxWithLabel::new(
|
||||
"use-server-name-in-ssh",
|
||||
Label::new("Use name as ssh connection string"),
|
||||
Label::new("Use SSH for terminals"),
|
||||
self.use_server_name_in_ssh,
|
||||
|&_, _| {}
|
||||
)
|
||||
|
@ -748,7 +767,7 @@ impl DevServerProjects {
|
|||
};
|
||||
div.px_2().child(Label::new(format!(
|
||||
"Once you have created a dev server, you will be given a command to run on the server to register it.\n\n\
|
||||
Ssh connection string enables remote terminals, which runs `ssh {ssh_host_name}` when creating terminal tabs."
|
||||
If you enable SSH, then the terminal will automatically `ssh {ssh_host_name}` on open."
|
||||
)))
|
||||
})
|
||||
.when_some(dev_server.clone(), |div, dev_server| {
|
||||
|
@ -758,7 +777,7 @@ impl DevServerProjects {
|
|||
.dev_server_status(DevServerId(dev_server.dev_server_id));
|
||||
|
||||
div.child(
|
||||
Self::render_dev_server_token_instructions(&dev_server.access_token, &dev_server.name, status, cx)
|
||||
self.render_dev_server_token_instructions(&dev_server.access_token, &dev_server.name, status, cx)
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
@ -766,12 +785,18 @@ impl DevServerProjects {
|
|||
}
|
||||
|
||||
fn render_dev_server_token_instructions(
|
||||
&self,
|
||||
access_token: &str,
|
||||
dev_server_name: &str,
|
||||
status: DevServerStatus,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Div {
|
||||
let instructions = SharedString::from(format!("zed --dev-server-token {}", access_token));
|
||||
self.markdown.update(cx, |markdown, cx| {
|
||||
if !markdown.source().contains(access_token) {
|
||||
markdown.reset(format!("```\n{}\n```", instructions), cx);
|
||||
}
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.pl_2()
|
||||
|
@ -799,19 +824,7 @@ impl DevServerProjects {
|
|||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().title_bar_background) // todo: this should be distinct
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.my_1()
|
||||
.py_0p5()
|
||||
.px_3()
|
||||
.font_family(ThemeSettings::get_global(cx).buffer_font.family.clone())
|
||||
.child(Label::new(instructions)),
|
||||
)
|
||||
.child(v_flex().w_full().child(self.markdown.clone()))
|
||||
.when(status == DevServerStatus::Offline, |this| {
|
||||
this.child(Self::render_loading_spinner("Waiting for connection…"))
|
||||
})
|
||||
|
@ -926,14 +939,13 @@ impl DevServerProjects {
|
|||
EditDevServerState::RegeneratingToken => {
|
||||
Self::render_loading_spinner("Generating token...")
|
||||
}
|
||||
EditDevServerState::RegeneratedToken(response) => {
|
||||
Self::render_dev_server_token_instructions(
|
||||
EditDevServerState::RegeneratedToken(response) => self
|
||||
.render_dev_server_token_instructions(
|
||||
&response.access_token,
|
||||
&dev_server_name,
|
||||
dev_server_status,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
),
|
||||
_ => h_flex().items_end().w_full().child(
|
||||
Button::new("regenerate-dev-server-token", "Generate new access token")
|
||||
.icon(IconName::Update)
|
||||
|
|
Loading…
Reference in a new issue