@@ -176,7 +176,7 @@ use workspace::{
};
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};
pub const FILE_HEADER_HEIGHT: u32 = 2;
@@ -9293,23 +9293,42 @@ impl Editor {
}
pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext<Self>) {
- let position = self.selections.newest_anchor().head();
- let Some((buffer, buffer_position)) =
- self.buffer.read(cx).text_anchor_for_position(position, cx)
+ let selection = self.selections.newest_anchor();
+ let head = selection.head();
+ let tail = selection.tail();
+
+ let Some((buffer, start_position)) =
+ self.buffer.read(cx).text_anchor_for_position(head, cx)
else {
return;
};
- cx.spawn(|editor, mut cx| async move {
- if let Some((_, url)) = find_url(&buffer, buffer_position, cx.clone()) {
+ let end_position = if head != tail {
+ 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| {
cx.open_url(&url);
})
} else {
Ok(())
}
- })
- .detach();
+ });
+
+ url_finder.detach();
}
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
@@ -694,6 +694,65 @@ pub(crate) fn find_url(
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(
buffer: &Model<language::Buffer>,
project: Option<Model<Project>>,