Improve editor open URL command to open the selected portion of URL (#21825)
Closes #21718 Just like in Vim, if a URL is selected, it opens exactly that portion of the URL. Otherwise, if only the cursor is on a URL, it opens the entire URL. Zed currently does the latter. This PR also adds support for the former. https://github.com/user-attachments/assets/8bdd2952-ceec-487c-b27a-5cea4258eb03 Release Notes: - Updated the `editor: open url` to also handle the selected portion of a URL.
This commit is contained in:
parent
096bbfead5
commit
5318f529de
2 changed files with 86 additions and 8 deletions
|
@ -176,7 +176,7 @@ use workspace::{
|
||||||
};
|
};
|
||||||
use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
|
use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
|
||||||
|
|
||||||
use crate::hover_links::find_url;
|
use crate::hover_links::{find_url, find_url_from_range};
|
||||||
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
|
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
|
||||||
|
|
||||||
pub const FILE_HEADER_HEIGHT: u32 = 2;
|
pub const FILE_HEADER_HEIGHT: u32 = 2;
|
||||||
|
@ -9293,23 +9293,42 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
|
pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
|
||||||
let position = self.selections.newest_anchor().head();
|
let selection = self.selections.newest_anchor();
|
||||||
let Some((buffer, buffer_position)) =
|
let head = selection.head();
|
||||||
self.buffer.read(cx).text_anchor_for_position(position, cx)
|
let tail = selection.tail();
|
||||||
|
|
||||||
|
let Some((buffer, start_position)) =
|
||||||
|
self.buffer.read(cx).text_anchor_for_position(head, cx)
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn(|editor, mut cx| async move {
|
let end_position = if head != tail {
|
||||||
if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
|
let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
Some(pos)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let url_finder = cx.spawn(|editor, mut cx| async move {
|
||||||
|
let url = if let Some(end_pos) = end_position {
|
||||||
|
find_url_from_range(&buffer, start_position..end_pos, cx.clone())
|
||||||
|
} else {
|
||||||
|
find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = url {
|
||||||
editor.update(&mut cx, |_, cx| {
|
editor.update(&mut cx, |_, cx| {
|
||||||
cx.open_url(&url);
|
cx.open_url(&url);
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.detach();
|
|
||||||
|
url_finder.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
|
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -694,6 +694,65 @@ pub(crate) fn find_url(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_url_from_range(
|
||||||
|
buffer: &Model<language::Buffer>,
|
||||||
|
range: Range<text::Anchor>,
|
||||||
|
mut cx: AsyncWindowContext,
|
||||||
|
) -> Option<String> {
|
||||||
|
const LIMIT: usize = 2048;
|
||||||
|
|
||||||
|
let Ok(snapshot) = buffer.update(&mut cx, |buffer, _| buffer.snapshot()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_offset = range.start.to_offset(&snapshot);
|
||||||
|
let end_offset = range.end.to_offset(&snapshot);
|
||||||
|
|
||||||
|
let mut token_start = start_offset.min(end_offset);
|
||||||
|
let mut token_end = start_offset.max(end_offset);
|
||||||
|
|
||||||
|
let range_len = token_end - token_start;
|
||||||
|
|
||||||
|
if range_len >= LIMIT {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip leading whitespace
|
||||||
|
for ch in snapshot.chars_at(token_start).take(range_len) {
|
||||||
|
if !ch.is_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
token_start += ch.len_utf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip trailing whitespace
|
||||||
|
for ch in snapshot.reversed_chars_at(token_end).take(range_len) {
|
||||||
|
if !ch.is_whitespace() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
token_end -= ch.len_utf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
if token_start >= token_end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = snapshot
|
||||||
|
.text_for_range(token_start..token_end)
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let mut finder = LinkFinder::new();
|
||||||
|
finder.kinds(&[LinkKind::Url]);
|
||||||
|
|
||||||
|
if let Some(link) = finder.links(&text).next() {
|
||||||
|
if link.start() == 0 && link.end() == text.len() {
|
||||||
|
return Some(link.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn find_file(
|
pub(crate) async fn find_file(
|
||||||
buffer: &Model<language::Buffer>,
|
buffer: &Model<language::Buffer>,
|
||||||
project: Option<Model<Project>>,
|
project: Option<Model<Project>>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue