From 7c67612b8a894187fa3b64725531a5459f9211bf Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 10 Aug 2018 22:33:29 +0300 Subject: organizize --- crates/libeditor/src/extend_selection.rs | 36 +++++++ crates/libeditor/src/lib.rs | 155 +++++++++++++++++++++++++++++++ crates/libeditor/src/line_index.rs | 62 +++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 crates/libeditor/src/extend_selection.rs create mode 100644 crates/libeditor/src/lib.rs create mode 100644 crates/libeditor/src/line_index.rs (limited to 'crates/libeditor/src') diff --git a/crates/libeditor/src/extend_selection.rs b/crates/libeditor/src/extend_selection.rs new file mode 100644 index 000000000..16d4bc084 --- /dev/null +++ b/crates/libeditor/src/extend_selection.rs @@ -0,0 +1,36 @@ +use libsyntax2::{ + TextRange, SyntaxNodeRef, + SyntaxKind::WHITESPACE, + algo::{find_leaf_at_offset, find_covering_node, ancestors}, +}; + + +pub(crate) fn extend_selection(root: SyntaxNodeRef, range: TextRange) -> Option { + if range.is_empty() { + let offset = range.start(); + let mut leaves = find_leaf_at_offset(root, offset); + if let Some(leaf) = leaves.clone().find(|node| node.kind() != WHITESPACE) { + return Some(leaf.range()); + } + let ws = leaves.next()?; +// let ws_suffix = file.text().slice( +// TextRange::from_to(offset, ws.range().end()) +// ); +// if ws.text().contains("\n") && !ws_suffix.contains("\n") { +// if let Some(line_end) = file.text() +// .slice(TextSuffix::from(ws.range().end())) +// .find("\n") +// { +// let range = TextRange::from_len(ws.range().end(), line_end); +// return Some(find_covering_node(file.root(), range).range()); +// } +// } + return Some(ws.range()); + }; + let node = find_covering_node(root, range); + + match ancestors(node).skip_while(|n| n.range() == range).next() { + None => None, + Some(parent) => Some(parent.range()), + } +} diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs new file mode 100644 index 000000000..f77647338 --- /dev/null +++ b/crates/libeditor/src/lib.rs @@ -0,0 +1,155 @@ +extern crate libsyntax2; +extern crate superslice; + +mod extend_selection; +mod line_index; + +use libsyntax2::{ + SyntaxNodeRef, AstNode, + algo::walk, + SyntaxKind::*, +}; +pub use libsyntax2::{TextRange, TextUnit, ast}; +pub use self::line_index::{LineIndex, LineCol}; + +#[derive(Debug)] +pub struct HighlightedRange { + pub range: TextRange, + pub tag: &'static str, +} + +#[derive(Debug)] +pub struct Diagnostic { + pub range: TextRange, + pub msg: String, +} + +#[derive(Debug)] +pub struct Symbol { + // pub parent: ???, + pub name: String, + pub range: TextRange, +} + +#[derive(Debug)] +pub struct Runnable { + pub range: TextRange, + pub kind: RunnableKind, +} + +#[derive(Debug)] +pub enum RunnableKind { + Test { name: String }, + Bin, +} + +pub fn highlight(file: &ast::File) -> Vec { + let syntax = file.syntax(); + let mut res = Vec::new(); + for node in walk::preorder(syntax.as_ref()) { + let tag = match node.kind() { + ERROR => "error", + COMMENT | DOC_COMMENT => "comment", + STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", + ATTR => "attribute", + NAME_REF => "text", + NAME => "function", + INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", + LIFETIME => "parameter", + k if k.is_keyword() => "keyword", + _ => continue, + }; + res.push(HighlightedRange { + range: node.range(), + tag, + }) + } + res +} + +pub fn diagnostics(file: &ast::File) -> Vec { + let syntax = file.syntax(); + let mut res = Vec::new(); + + for node in walk::preorder(syntax.as_ref()) { + if node.kind() == ERROR { + res.push(Diagnostic { + range: node.range(), + msg: "Syntax Error".to_string(), + }); + } + } + res.extend(file.errors().into_iter().map(|err| Diagnostic { + range: TextRange::offset_len(err.offset, 1.into()), + msg: err.msg, + })); + res +} + +pub fn syntax_tree(file: &ast::File) -> String { + ::libsyntax2::utils::dump_tree(&file.syntax()) +} + +pub fn symbols(file: &ast::File) -> Vec { + let syntax = file.syntax(); + let res: Vec = walk::preorder(syntax.as_ref()) + .filter_map(Declaration::cast) + .filter_map(|decl| { + let name = decl.name()?; + let range = decl.range(); + Some(Symbol { name, range }) + }) + .collect(); + res // NLL :-( +} + +pub fn extend_selection(file: &ast::File, range: TextRange) -> Option { + let syntax = file.syntax(); + extend_selection::extend_selection(syntax.as_ref(), range) +} + +pub fn runnables(file: &ast::File) -> Vec { + file + .functions() + .filter_map(|f| { + let name = f.name()?.text(); + let kind = if name == "main" { + RunnableKind::Bin + } else if f.has_atom_attr("test") { + RunnableKind::Test { + name: name.to_string() + } + } else { + return None; + }; + Some(Runnable { + range: f.syntax().range(), + kind, + }) + }) + .collect() +} + + +struct Declaration<'f> (SyntaxNodeRef<'f>); + +impl<'f> Declaration<'f> { + fn cast(node: SyntaxNodeRef<'f>) -> Option> { + match node.kind() { + | STRUCT_ITEM | ENUM_ITEM | FUNCTION | TRAIT_ITEM + | CONST_ITEM | STATIC_ITEM | MOD_ITEM | NAMED_FIELD + | TYPE_ITEM => Some(Declaration(node)), + _ => None + } + } + + fn name(&self) -> Option { + let name = self.0.children() + .find(|child| child.kind() == NAME)?; + Some(name.text()) + } + + fn range(&self) -> TextRange { + self.0.range() + } +} diff --git a/crates/libeditor/src/line_index.rs b/crates/libeditor/src/line_index.rs new file mode 100644 index 000000000..801726aa5 --- /dev/null +++ b/crates/libeditor/src/line_index.rs @@ -0,0 +1,62 @@ +use superslice::Ext; +use ::TextUnit; + +#[derive(Clone, Debug)] +pub struct LineIndex { + newlines: Vec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct LineCol { + pub line: u32, + pub col: TextUnit, +} + +impl LineIndex { + pub fn new(text: &str) -> LineIndex { + let mut newlines = vec![0.into()]; + let mut curr = 0.into(); + for c in text.chars() { + curr += TextUnit::of_char(c); + if c == '\n' { + newlines.push(curr); + } + } + LineIndex { newlines } + } + + pub fn line_col(&self, offset: TextUnit) -> LineCol { + let line = self.newlines.upper_bound(&offset) - 1; + let line_start_offset = self.newlines[line]; + let col = offset - line_start_offset; + return LineCol { line: line as u32, col }; + } + + pub fn offset(&self, line_col: LineCol) -> TextUnit { + //TODO: return Result + self.newlines[line_col.line as usize] + line_col.col + } +} + +#[test] +fn test_line_index() { + let text = "hello\nworld"; + let index = LineIndex::new(text); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); + assert_eq!(index.line_col(1.into()), LineCol { line: 0, col: 1.into() }); + assert_eq!(index.line_col(5.into()), LineCol { line: 0, col: 5.into() }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 0.into() }); + assert_eq!(index.line_col(7.into()), LineCol { line: 1, col: 1.into() }); + assert_eq!(index.line_col(8.into()), LineCol { line: 1, col: 2.into() }); + assert_eq!(index.line_col(10.into()), LineCol { line: 1, col: 4.into() }); + assert_eq!(index.line_col(11.into()), LineCol { line: 1, col: 5.into() }); + assert_eq!(index.line_col(12.into()), LineCol { line: 1, col: 6.into() }); + + let text = "\nhello\nworld"; + let index = LineIndex::new(text); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() }); + assert_eq!(index.line_col(1.into()), LineCol { line: 1, col: 0.into() }); + assert_eq!(index.line_col(2.into()), LineCol { line: 1, col: 1.into() }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 5.into() }); + assert_eq!(index.line_col(7.into()), LineCol { line: 2, col: 0.into() }); +} -- cgit v1.2.3