mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
a simple code for html tag support, I've only done the basics, and if it's okay, I'll optimize and organize the code, and adapt other parts like `is_multiline`, `always_expands_both_ways`, `target_visual_mode`, etc --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
a42b987929
commit
f3fa3b910a
@ -383,6 +383,7 @@
|
|||||||
"ignorePunctuation": true
|
"ignorePunctuation": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"t": "vim::Tag",
|
||||||
"s": "vim::Sentence",
|
"s": "vim::Sentence",
|
||||||
"'": "vim::Quotes",
|
"'": "vim::Quotes",
|
||||||
"`": "vim::BackQuotes",
|
"`": "vim::BackQuotes",
|
||||||
|
@ -21,6 +21,7 @@ test-support = [
|
|||||||
"workspace/test-support",
|
"workspace/test-support",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
|
"tree-sitter-html"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -214,6 +214,22 @@ impl EditorLspTestContext {
|
|||||||
Self::new(language, capabilities, cx).await
|
Self::new(language, capabilities, cx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn new_html(cx: &mut gpui::TestAppContext) -> Self {
|
||||||
|
let language = Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "HTML".into(),
|
||||||
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["html".into()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
block_comment: Some(("<!-- ".into(), " -->".into())),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_html::language()),
|
||||||
|
);
|
||||||
|
Self::new(language, Default::default(), cx).await
|
||||||
|
}
|
||||||
|
|
||||||
// Constructs lsp range using a marked string with '[', ']' range delimiters
|
// Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||||
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
||||||
let ranges = self.ranges(marked_text);
|
let ranges = self.ranges(marked_text);
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
motion::right, normal::normal_object, state::Mode, utils::coerce_punctuation,
|
||||||
|
visual::visual_object, Vim,
|
||||||
|
};
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
movement::{self, FindRange},
|
movement::{self, FindRange},
|
||||||
@ -10,11 +14,6 @@ use language::{char_kind, BufferSnapshot, CharKind, Selection};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
motion::right, normal::normal_object, state::Mode, utils::coerce_punctuation,
|
|
||||||
visual::visual_object, Vim,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Object {
|
pub enum Object {
|
||||||
Word { ignore_punctuation: bool },
|
Word { ignore_punctuation: bool },
|
||||||
@ -28,6 +27,7 @@ pub enum Object {
|
|||||||
CurlyBrackets,
|
CurlyBrackets,
|
||||||
AngleBrackets,
|
AngleBrackets,
|
||||||
Argument,
|
Argument,
|
||||||
|
Tag,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
@ -51,7 +51,8 @@ actions!(
|
|||||||
SquareBrackets,
|
SquareBrackets,
|
||||||
CurlyBrackets,
|
CurlyBrackets,
|
||||||
AngleBrackets,
|
AngleBrackets,
|
||||||
Argument
|
Argument,
|
||||||
|
Tag
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
|||||||
object(Object::Word { ignore_punctuation }, cx)
|
object(Object::Word { ignore_punctuation }, cx)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
workspace.register_action(|_: &mut Workspace, _: &Tag, cx: _| object(Object::Tag, cx));
|
||||||
workspace
|
workspace
|
||||||
.register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
|
.register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx));
|
||||||
workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
|
workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx));
|
||||||
@ -108,6 +110,7 @@ impl Object {
|
|||||||
| Object::DoubleQuotes => false,
|
| Object::DoubleQuotes => false,
|
||||||
Object::Sentence
|
Object::Sentence
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
|
| Object::Tag
|
||||||
| Object::AngleBrackets
|
| Object::AngleBrackets
|
||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
| Object::SquareBrackets
|
| Object::SquareBrackets
|
||||||
@ -124,6 +127,7 @@ impl Object {
|
|||||||
| Object::VerticalBars
|
| Object::VerticalBars
|
||||||
| Object::Parentheses
|
| Object::Parentheses
|
||||||
| Object::SquareBrackets
|
| Object::SquareBrackets
|
||||||
|
| Object::Tag
|
||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
| Object::AngleBrackets => true,
|
| Object::AngleBrackets => true,
|
||||||
}
|
}
|
||||||
@ -147,6 +151,7 @@ impl Object {
|
|||||||
| Object::CurlyBrackets
|
| Object::CurlyBrackets
|
||||||
| Object::AngleBrackets
|
| Object::AngleBrackets
|
||||||
| Object::VerticalBars
|
| Object::VerticalBars
|
||||||
|
| Object::Tag
|
||||||
| Object::Argument => Mode::Visual,
|
| Object::Argument => Mode::Visual,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,6 +186,7 @@ impl Object {
|
|||||||
Object::Parentheses => {
|
Object::Parentheses => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '(', ')')
|
||||||
}
|
}
|
||||||
|
Object::Tag => surrounding_html_tag(map, relative_to, around),
|
||||||
Object::SquareBrackets => {
|
Object::SquareBrackets => {
|
||||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
|
surrounding_markers(map, relative_to, around, self.is_multiline(), '[', ']')
|
||||||
}
|
}
|
||||||
@ -241,6 +247,72 @@ fn in_word(
|
|||||||
Some(start..end)
|
Some(start..end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn surrounding_html_tag(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
relative_to: DisplayPoint,
|
||||||
|
surround: bool,
|
||||||
|
) -> Option<Range<DisplayPoint>> {
|
||||||
|
fn read_tag(chars: impl Iterator<Item = char>) -> String {
|
||||||
|
chars
|
||||||
|
.take_while(|c| c.is_alphanumeric() || *c == ':' || *c == '-' || *c == '_' || *c == '.')
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
fn open_tag(mut chars: impl Iterator<Item = char>) -> Option<String> {
|
||||||
|
if Some('<') != chars.next() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(read_tag(chars))
|
||||||
|
}
|
||||||
|
fn close_tag(mut chars: impl Iterator<Item = char>) -> Option<String> {
|
||||||
|
if (Some('<'), Some('/')) != (chars.next(), chars.next()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(read_tag(chars))
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapshot = &map.buffer_snapshot;
|
||||||
|
let offset = relative_to.to_offset(map, Bias::Left);
|
||||||
|
let excerpt = snapshot.excerpt_containing(offset..offset)?;
|
||||||
|
let buffer = excerpt.buffer();
|
||||||
|
let offset = excerpt.map_offset_to_buffer(offset);
|
||||||
|
|
||||||
|
// Find the most closest to current offset
|
||||||
|
let mut cursor = buffer.syntax_layer_at(offset)?.node().walk();
|
||||||
|
let mut last_child_node = cursor.node();
|
||||||
|
while cursor.goto_first_child_for_byte(offset).is_some() {
|
||||||
|
last_child_node = cursor.node();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_child_node = Some(last_child_node);
|
||||||
|
while let Some(cur_node) = last_child_node {
|
||||||
|
if cur_node.child_count() >= 2 {
|
||||||
|
let first_child = cur_node.child(0);
|
||||||
|
let last_child = cur_node.child(cur_node.child_count() - 1);
|
||||||
|
if let (Some(first_child), Some(last_child)) = (first_child, last_child) {
|
||||||
|
let open_tag = open_tag(buffer.chars_for_range(first_child.byte_range()));
|
||||||
|
let close_tag = close_tag(buffer.chars_for_range(last_child.byte_range()));
|
||||||
|
if open_tag.is_some()
|
||||||
|
&& open_tag == close_tag
|
||||||
|
&& (first_child.end_byte() + 1..last_child.start_byte()).contains(&offset)
|
||||||
|
{
|
||||||
|
let range = if surround {
|
||||||
|
first_child.byte_range().start..last_child.byte_range().end
|
||||||
|
} else {
|
||||||
|
first_child.byte_range().end..last_child.byte_range().start
|
||||||
|
};
|
||||||
|
if excerpt.contains_buffer_range(range.clone()) {
|
||||||
|
let result = excerpt.map_range_from_buffer(range);
|
||||||
|
return Some(
|
||||||
|
result.start.to_display_point(map)..result.end.to_display_point(map),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_child_node = cur_node.parent();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
/// Returns a range that surrounds the word and following whitespace
|
/// Returns a range that surrounds the word and following whitespace
|
||||||
/// relative_to is in.
|
/// relative_to is in.
|
||||||
///
|
///
|
||||||
@ -1246,4 +1318,26 @@ mod test {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_tags(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new_html(cx).await;
|
||||||
|
|
||||||
|
cx.set_state("<html><head></head><body><b>hˇi!</b></body>", Mode::Normal);
|
||||||
|
cx.simulate_keystrokes(["v", "i", "t"]);
|
||||||
|
cx.assert_state(
|
||||||
|
"<html><head></head><body><b>«hi!ˇ»</b></body>",
|
||||||
|
Mode::Visual,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes(["a", "t"]);
|
||||||
|
cx.assert_state(
|
||||||
|
"<html><head></head><body>«<b>hi!</b>ˇ»</body>",
|
||||||
|
Mode::Visual,
|
||||||
|
);
|
||||||
|
cx.simulate_keystrokes(["a", "t"]);
|
||||||
|
cx.assert_state(
|
||||||
|
"<html><head></head>«<body><b>hi!</b></body>ˇ»",
|
||||||
|
Mode::Visual,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,11 @@ impl VimTestContext {
|
|||||||
Self::new_with_lsp(lsp, enabled)
|
Self::new_with_lsp(lsp, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn new_html(cx: &mut gpui::TestAppContext) -> VimTestContext {
|
||||||
|
Self::init(cx);
|
||||||
|
Self::new_with_lsp(EditorLspTestContext::new_html(cx).await, true)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new_typescript(cx: &mut gpui::TestAppContext) -> VimTestContext {
|
pub async fn new_typescript(cx: &mut gpui::TestAppContext) -> VimTestContext {
|
||||||
Self::init(cx);
|
Self::init(cx);
|
||||||
Self::new_with_lsp(
|
Self::new_with_lsp(
|
||||||
|
Loading…
Reference in New Issue
Block a user