aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--codeless/package.json7
-rw-r--r--codeless/server/src/dispatch.rs23
-rw-r--r--codeless/server/src/handlers.rs58
-rw-r--r--codeless/server/src/main.rs50
-rw-r--r--codeless/server/src/req.rs9
-rw-r--r--libanalysis/Cargo.toml2
-rw-r--r--libanalysis/src/arena.rs42
-rw-r--r--libanalysis/src/lib.rs89
-rw-r--r--libeditor/src/lib.rs1
-rw-r--r--libeditor/src/line_index.rs40
10 files changed, 198 insertions, 123 deletions
diff --git a/codeless/package.json b/codeless/package.json
index 7a00f33ef..1df932db1 100644
--- a/codeless/package.json
+++ b/codeless/package.json
@@ -36,6 +36,13 @@
36 "command": "libsyntax-rust.extendSelection", 36 "command": "libsyntax-rust.extendSelection",
37 "title": "Rust Extend Selection" 37 "title": "Rust Extend Selection"
38 } 38 }
39 ],
40 "keybindings": [
41 {
42 "command": "libsyntax-rust.extendSelection",
43 "key": "ctrl+w",
44 "when": "editorTextFocus && editorLangId == rust"
45 }
39 ] 46 ]
40 } 47 }
41} 48}
diff --git a/codeless/server/src/dispatch.rs b/codeless/server/src/dispatch.rs
index 41437b62a..2da0996e3 100644
--- a/codeless/server/src/dispatch.rs
+++ b/codeless/server/src/dispatch.rs
@@ -52,7 +52,7 @@ impl<R: Request> Responder<R>
52} 52}
53 53
54 54
55pub fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>> 55fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder<R>), RawRequest>>
56 where 56 where
57 R: Request, 57 R: Request,
58 R::Params: DeserializeOwned, 58 R::Params: DeserializeOwned,
@@ -71,6 +71,25 @@ pub fn parse_request_as<R>(raw: RawRequest) -> Result<::std::result::Result<(R::
71 Ok(Ok((params, responder))) 71 Ok(Ok((params, responder)))
72} 72}
73 73
74pub fn handle_request<R, F>(req: &mut Option<RawRequest>, f: F) -> Result<()>
75 where
76 R: Request,
77 R::Params: DeserializeOwned,
78 R::Result: Serialize,
79 F: FnOnce(R::Params, Responder<R>) -> Result<()>
80{
81 match req.take() {
82 None => Ok(()),
83 Some(r) => match parse_request_as::<R>(r)? {
84 Ok((params, responder)) => f(params, responder),
85 Err(r) => {
86 *req = Some(r);
87 Ok(())
88 },
89 }
90 }
91}
92
74pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>> 93pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Params, Responder<R>)>>
75 where 94 where
76 R: Request, 95 R: Request,
@@ -87,7 +106,7 @@ pub fn expect_request<R>(io: &mut Io, raw: RawRequest) -> Result<Option<(R::Para
87 Ok(ret) 106 Ok(ret)
88} 107}
89 108
90pub fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>> 109fn parse_notification_as<N>(raw: RawNotification) -> Result<::std::result::Result<N::Params, RawNotification>>
91 where 110 where
92 N: Notification, 111 N: Notification,
93 N::Params: DeserializeOwned, 112 N::Params: DeserializeOwned,
diff --git a/codeless/server/src/handlers.rs b/codeless/server/src/handlers.rs
index 3f257941a..5ee87a4dd 100644
--- a/codeless/server/src/handlers.rs
+++ b/codeless/server/src/handlers.rs
@@ -1,13 +1,61 @@
1use languageserver_types::{Range, Position};
1use libanalysis::World; 2use libanalysis::World;
2use libeditor; 3use libeditor::{self, LineIndex, LineCol, TextRange, TextUnit};
3use {req, Result}; 4use {req, Result, FilePath};
4 5
5pub fn handle_syntax_tree( 6pub fn handle_syntax_tree(
6 world: World, 7 world: World,
7 params: req::SyntaxTreeParams 8 params: req::SyntaxTreeParams,
8) -> Result<String> { 9) -> Result<String> {
9 let path = params.text_document.uri.to_file_path() 10 let path = params.text_document.file_path()?;
10 .map_err(|()| format_err!("invalid path"))?;
11 let file = world.file_syntax(&path)?; 11 let file = world.file_syntax(&path)?;
12 Ok(libeditor::syntax_tree(&file)) 12 Ok(libeditor::syntax_tree(&file))
13} 13}
14
15pub fn handle_extend_selection(
16 world: World,
17 params: req::ExtendSelectionParams,
18) -> Result<req::ExtendSelectionResult> {
19 let path = params.text_document.file_path()?;
20 let file = world.file_syntax(&path)?;
21 let line_index = world.file_line_index(&path)?;
22 let selections = params.selections.into_iter()
23 .map(|r| {
24 let r = to_text_range(&line_index, r);
25 let r = libeditor::extend_selection(&file, r).unwrap_or(r);
26 to_vs_range(&line_index, r)
27 })
28 .collect();
29 Ok(req::ExtendSelectionResult { selections })
30}
31
32
33fn to_text_range(line_index: &LineIndex, range: Range) -> TextRange {
34 TextRange::from_to(
35 to_text_unit(line_index, range.start),
36 to_text_unit(line_index, range.end),
37 )
38}
39
40fn to_text_unit(line_index: &LineIndex, position: Position) -> TextUnit {
41 // TODO: UTF-16
42 let line_col = LineCol {
43 line: position.line as u32,
44 col: (position.character as u32).into(),
45 };
46 line_index.offset(line_col)
47}
48
49
50fn to_vs_range(line_index: &LineIndex, range: TextRange) -> Range {
51 Range::new(
52 to_vs_position(line_index, range.start()),
53 to_vs_position(line_index, range.end()),
54 )
55}
56
57fn to_vs_position(line_index: &LineIndex, offset: TextUnit) -> Position {
58 let line_col = line_index.line_col(offset);
59 // TODO: UTF-16
60 Position::new(line_col.line as u64, u32::from(line_col.col) as u64)
61}
diff --git a/codeless/server/src/main.rs b/codeless/server/src/main.rs
index 287d650fa..116abce1c 100644
--- a/codeless/server/src/main.rs
+++ b/codeless/server/src/main.rs
@@ -31,7 +31,7 @@ use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, Te
31 31
32use ::{ 32use ::{
33 io::{Io, RawMsg}, 33 io::{Io, RawMsg},
34 handlers::handle_syntax_tree, 34 handlers::{handle_syntax_tree, handle_extend_selection},
35}; 35};
36 36
37pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 37pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
@@ -153,34 +153,42 @@ fn main_loop(
153 153
154 match msg { 154 match msg {
155 RawMsg::Request(req) => { 155 RawMsg::Request(req) => {
156 let req = match dispatch::parse_request_as::<req::SyntaxTree>(req)? { 156 let mut req = Some(req);
157 Ok((params, resp)) => { 157 dispatch::handle_request::<req::SyntaxTree, _>(&mut req, |params, resp| {
158 let world = world.snapshot(); 158 let world = world.snapshot();
159 let sender = sender.clone(); 159 let sender = sender.clone();
160 pool.execute(move || { 160 pool.execute(move || {
161 let res: Result<String> = handle_syntax_tree(world, params); 161 let res = handle_syntax_tree(world, params);
162 sender.send(Box::new(|io: &mut Io| resp.response(io, res))) 162 sender.send(Box::new(|io: &mut Io| resp.response(io, res)))
163 }); 163 });
164 continue; 164 Ok(())
165 } 165 })?;
166 Err(req) => req, 166 dispatch::handle_request::<req::ExtendSelection, _>(&mut req, |params, resp| {
167 }; 167 let world = world.snapshot();
168 168 let sender = sender.clone();
169 if let Some(((), resp)) = dispatch::expect_request::<req::Shutdown>(io, req)? { 169 pool.execute(move || {
170 info!("clean shutdown started"); 170 let res = handle_extend_selection(world, params);
171 sender.send(Box::new(|io: &mut Io| resp.response(io, res)))
172 });
173 Ok(())
174 })?;
175 dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| {
171 resp.result(io, ())?; 176 resp.result(io, ())?;
172 return Ok(()); 177 Ok(())
178 })?;
179 if let Some(req) = req {
180 error!("unknown method: {:?}", req);
181 dispatch::unknown_method(io, req)?;
173 } 182 }
174 } 183 }
175 RawMsg::Notification(not) => { 184 RawMsg::Notification(not) => {
176 use dispatch::handle_notification as h;
177 let mut not = Some(not); 185 let mut not = Some(not);
178 h::<req::DidOpenTextDocument, _>(&mut not, |params| { 186 dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| {
179 let path = params.text_document.file_path()?; 187 let path = params.text_document.file_path()?;
180 world.change_overlay(path, Some(params.text_document.text)); 188 world.change_overlay(path, Some(params.text_document.text));
181 Ok(()) 189 Ok(())
182 })?; 190 })?;
183 h::<req::DidChangeTextDocument, _>(&mut not, |mut params| { 191 dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| {
184 let path = params.text_document.file_path()?; 192 let path = params.text_document.file_path()?;
185 let text = params.content_changes.pop() 193 let text = params.content_changes.pop()
186 .ok_or_else(|| format_err!("empty changes"))? 194 .ok_or_else(|| format_err!("empty changes"))?
@@ -188,7 +196,7 @@ fn main_loop(
188 world.change_overlay(path, Some(text)); 196 world.change_overlay(path, Some(text));
189 Ok(()) 197 Ok(())
190 })?; 198 })?;
191 h::<req::DidCloseTextDocument, _>(&mut not, |params| { 199 dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| {
192 let path = params.text_document.file_path()?; 200 let path = params.text_document.file_path()?;
193 world.change_overlay(path, None); 201 world.change_overlay(path, None);
194 Ok(()) 202 Ok(())
diff --git a/codeless/server/src/req.rs b/codeless/server/src/req.rs
index ee4a786c7..4e588159b 100644
--- a/codeless/server/src/req.rs
+++ b/codeless/server/src/req.rs
@@ -21,6 +21,12 @@ pub struct SyntaxTreeParams {
21 21
22pub enum ExtendSelection {} 22pub enum ExtendSelection {}
23 23
24impl Request for ExtendSelection {
25 type Params = ExtendSelectionParams;
26 type Result = ExtendSelectionResult;
27 const METHOD: &'static str = "m/extendSelection";
28}
29
24#[derive(Deserialize, Debug)] 30#[derive(Deserialize, Debug)]
25#[serde(rename_all = "camelCase")] 31#[serde(rename_all = "camelCase")]
26pub struct ExtendSelectionParams { 32pub struct ExtendSelectionParams {
@@ -28,7 +34,8 @@ pub struct ExtendSelectionParams {
28 pub selections: Vec<Range>, 34 pub selections: Vec<Range>,
29} 35}
30 36
31 37#[derive(Serialize, Debug)]
38#[serde(rename_all = "camelCase")]
32pub struct ExtendSelectionResult { 39pub struct ExtendSelectionResult {
33 pub selections: Vec<Range>, 40 pub selections: Vec<Range>,
34} 41}
diff --git a/libanalysis/Cargo.toml b/libanalysis/Cargo.toml
index 737463258..2beea5640 100644
--- a/libanalysis/Cargo.toml
+++ b/libanalysis/Cargo.toml
@@ -7,4 +7,6 @@ authors = ["Aleksey Kladov <[email protected]>"]
7log = "0.4.2" 7log = "0.4.2"
8failure = "0.1.2" 8failure = "0.1.2"
9parking_lot = "0.6.3" 9parking_lot = "0.6.3"
10once_cell = "0.1.4"
10libsyntax2 = { path = "../" } 11libsyntax2 = { path = "../" }
12libeditor = { path = "../libeditor" }
diff --git a/libanalysis/src/arena.rs b/libanalysis/src/arena.rs
deleted file mode 100644
index fc0c25c54..000000000
--- a/libanalysis/src/arena.rs
+++ /dev/null
@@ -1,42 +0,0 @@
1use parking_lot::RwLock;
2
3const CHUNK_LEN: usize = 16;
4
5pub struct Arena<T> {
6 chunks: RwLock<Vec<Vec<T>>>,
7}
8
9impl<T> Arena<T> {
10 pub fn new(&self) -> Arena<T> {
11 Arena {
12 chunks: RwLock::new(vec![Vec::with_capacity(CHUNK_LEN)]),
13 }
14 }
15
16 pub fn push(&self, value: T) -> usize {
17 let mut guard = self.chunks.write();
18 let mut idx = (guard.len() - 1) * CHUNK_LEN;
19 let chunk = {
20 if guard.last().unwrap().len() == CHUNK_LEN {
21 guard.push(Vec::with_capacity(CHUNK_LEN));
22 }
23 guard.last_mut().unwrap()
24 };
25 assert!(chunk.len() < chunk.capacity());
26 idx += chunk.len();
27 chunk.push(value);
28 idx
29 }
30
31 pub fn get(&self, idx: usize) -> &T {
32 let chunk_idx = idx / CHUNK_LEN;
33 let chunk_off = idx - chunk_idx * CHUNK_LEN;
34 let guard = self.chunks.read();
35 let value = &guard[chunk_idx][chunk_off];
36 unsafe {
37 // We are careful to not move values in chunks,
38 // so this hopefully is safe
39 ::std::mem::transmute::<&T, &T>(value)
40 }
41 }
42}
diff --git a/libanalysis/src/lib.rs b/libanalysis/src/lib.rs
index 417a544ca..6a946a0b0 100644
--- a/libanalysis/src/lib.rs
+++ b/libanalysis/src/lib.rs
@@ -2,9 +2,11 @@ extern crate failure;
2extern crate parking_lot; 2extern crate parking_lot;
3#[macro_use] 3#[macro_use]
4extern crate log; 4extern crate log;
5extern crate once_cell;
5extern crate libsyntax2; 6extern crate libsyntax2;
7extern crate libeditor;
6 8
7mod arena; 9use once_cell::sync::OnceCell;
8 10
9use std::{ 11use std::{
10 fs, 12 fs,
@@ -14,6 +16,7 @@ use std::{
14}; 16};
15use parking_lot::RwLock; 17use parking_lot::RwLock;
16use libsyntax2::ast; 18use libsyntax2::ast;
19use libeditor::LineIndex;
17 20
18pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 21pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
19 22
@@ -39,7 +42,6 @@ impl WorldState {
39 pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) { 42 pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) {
40 let data = self.data_mut(); 43 let data = self.data_mut();
41 data.file_map.get_mut().remove(&path); 44 data.file_map.get_mut().remove(&path);
42 data.fs_map.get_mut().remove(&path);
43 if let Some(text) = text { 45 if let Some(text) = text {
44 data.mem_map.insert(path, Arc::new(text)); 46 data.mem_map.insert(path, Arc::new(text));
45 } else { 47 } else {
@@ -49,11 +51,9 @@ impl WorldState {
49 51
50 fn data_mut(&mut self) -> &mut WorldData { 52 fn data_mut(&mut self) -> &mut WorldData {
51 if Arc::get_mut(&mut self.data).is_none() { 53 if Arc::get_mut(&mut self.data).is_none() {
52 let fs_map = self.data.fs_map.read().clone();
53 let file_map = self.data.file_map.read().clone(); 54 let file_map = self.data.file_map.read().clone();
54 self.data = Arc::new(WorldData { 55 self.data = Arc::new(WorldData {
55 mem_map: self.data.mem_map.clone(), 56 mem_map: self.data.mem_map.clone(),
56 fs_map: RwLock::new(fs_map),
57 file_map: RwLock::new(file_map), 57 file_map: RwLock::new(file_map),
58 }); 58 });
59 } 59 }
@@ -64,43 +64,57 @@ impl WorldState {
64 64
65impl World { 65impl World {
66 pub fn file_syntax(&self, path: &Path) -> Result<ast::File> { 66 pub fn file_syntax(&self, path: &Path) -> Result<ast::File> {
67 { 67 let data = self.file_data(path)?;
68 let guard = self.data.file_map.read(); 68 let syntax = data.syntax
69 if let Some(file) = guard.get(path) { 69 .get_or_init(|| {
70 return Ok(file.clone()); 70 trace!("parsing: {}", path.display());
71 } 71 ast::File::parse(self.file_text(path, &data))
72 } 72 }).clone();
73 let file = self.with_file_text(path, |text| { 73 Ok(syntax)
74 trace!("parsing file: {}", path.display());
75 ast::File::parse(text)
76 })?;
77 let mut guard = self.data.file_map.write();
78 let file = guard.entry(path.to_owned())
79 .or_insert(file)
80 .clone();
81 Ok(file)
82 } 74 }
83 75
84 fn with_file_text<F: FnOnce(&str) -> R, R>(&self, path: &Path, f: F) -> Result<R> { 76 pub fn file_line_index(&self, path: &Path) -> Result<LineIndex> {
85 if let Some(text) = self.data.mem_map.get(path) { 77 let data = self.file_data(path)?;
86 return Ok(f(&*text)); 78 let index = data.lines
79 .get_or_init(|| {
80 trace!("calc line index: {}", path.display());
81 LineIndex::new(self.file_text(path, &data))
82 });
83 Ok(index.clone())
84 }
85
86 fn file_text<'a>(&'a self, path: &Path, file_data: &'a FileData) -> &'a str {
87 match file_data.text.as_ref() {
88 Some(text) => text.as_str(),
89 None => self.data.mem_map[path].as_str()
87 } 90 }
91 }
88 92
93 fn file_data(&self, path: &Path) -> Result<Arc<FileData>> {
89 { 94 {
90 let guard = self.data.fs_map.read(); 95 let guard = self.data.file_map.read();
91 if let Some(text) = guard.get(path) { 96 if let Some(data) = guard.get(path) {
92 return Ok(f(&*text)); 97 return Ok(data.clone());
93 } 98 }
94 } 99 }
95 trace!("loading file from disk: {}", path.display()); 100
96 let text = fs::read_to_string(path)?; 101 let text = if self.data.mem_map.contains_key(path) {
97 { 102 None
98 let mut guard = self.data.fs_map.write(); 103 } else {
104 trace!("loading file from disk: {}", path.display());
105 Some(fs::read_to_string(path)?)
106 };
107 let res = {
108 let mut guard = self.data.file_map.write();
99 guard.entry(path.to_owned()) 109 guard.entry(path.to_owned())
100 .or_insert_with(|| Arc::new(text)); 110 .or_insert_with(|| Arc::new(FileData {
101 } 111 text,
102 let guard = self.data.fs_map.read(); 112 syntax: OnceCell::new(),
103 Ok(f(&guard[path])) 113 lines: OnceCell::new(),
114 }))
115 .clone()
116 };
117 Ok(res)
104 } 118 }
105} 119}
106 120
@@ -108,6 +122,11 @@ impl World {
108#[derive(Default)] 122#[derive(Default)]
109struct WorldData { 123struct WorldData {
110 mem_map: HashMap<PathBuf, Arc<String>>, 124 mem_map: HashMap<PathBuf, Arc<String>>,
111 fs_map: RwLock<HashMap<PathBuf, Arc<String>>>, 125 file_map: RwLock<HashMap<PathBuf, Arc<FileData>>>,
112 file_map: RwLock<HashMap<PathBuf, ast::File>>, 126}
127
128struct FileData {
129 text: Option<String>,
130 syntax: OnceCell<ast::File>,
131 lines: OnceCell<LineIndex>,
113} 132}
diff --git a/libeditor/src/lib.rs b/libeditor/src/lib.rs
index 9da71743f..f77647338 100644
--- a/libeditor/src/lib.rs
+++ b/libeditor/src/lib.rs
@@ -10,6 +10,7 @@ use libsyntax2::{
10 SyntaxKind::*, 10 SyntaxKind::*,
11}; 11};
12pub use libsyntax2::{TextRange, TextUnit, ast}; 12pub use libsyntax2::{TextRange, TextUnit, ast};
13pub use self::line_index::{LineIndex, LineCol};
13 14
14#[derive(Debug)] 15#[derive(Debug)]
15pub struct HighlightedRange { 16pub struct HighlightedRange {
diff --git a/libeditor/src/line_index.rs b/libeditor/src/line_index.rs
index feb482b32..801726aa5 100644
--- a/libeditor/src/line_index.rs
+++ b/libeditor/src/line_index.rs
@@ -1,6 +1,7 @@
1use superslice::Ext; 1use superslice::Ext;
2use ::{TextUnit}; 2use ::TextUnit;
3 3
4#[derive(Clone, Debug)]
4pub struct LineIndex { 5pub struct LineIndex {
5 newlines: Vec<TextUnit>, 6 newlines: Vec<TextUnit>,
6} 7}
@@ -24,11 +25,16 @@ impl LineIndex {
24 LineIndex { newlines } 25 LineIndex { newlines }
25 } 26 }
26 27
27 pub fn translate(&self, offset: TextUnit) -> LineCol { 28 pub fn line_col(&self, offset: TextUnit) -> LineCol {
28 let line = self.newlines.upper_bound(&offset) - 1; 29 let line = self.newlines.upper_bound(&offset) - 1;
29 let line_start_offset = self.newlines[line]; 30 let line_start_offset = self.newlines[line];
30 let col = offset - line_start_offset; 31 let col = offset - line_start_offset;
31 return LineCol { line: line as u32, col } 32 return LineCol { line: line as u32, col };
33 }
34
35 pub fn offset(&self, line_col: LineCol) -> TextUnit {
36 //TODO: return Result
37 self.newlines[line_col.line as usize] + line_col.col
32 } 38 }
33} 39}
34 40
@@ -36,21 +42,21 @@ impl LineIndex {
36fn test_line_index() { 42fn test_line_index() {
37 let text = "hello\nworld"; 43 let text = "hello\nworld";
38 let index = LineIndex::new(text); 44 let index = LineIndex::new(text);
39 assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); 45 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() });
40 assert_eq!(index.translate(1.into()), LineCol { line: 0, col: 1.into()}); 46 assert_eq!(index.line_col(1.into()), LineCol { line: 0, col: 1.into() });
41 assert_eq!(index.translate(5.into()), LineCol { line: 0, col: 5.into()}); 47 assert_eq!(index.line_col(5.into()), LineCol { line: 0, col: 5.into() });
42 assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 0.into()}); 48 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 0.into() });
43 assert_eq!(index.translate(7.into()), LineCol { line: 1, col: 1.into()}); 49 assert_eq!(index.line_col(7.into()), LineCol { line: 1, col: 1.into() });
44 assert_eq!(index.translate(8.into()), LineCol { line: 1, col: 2.into()}); 50 assert_eq!(index.line_col(8.into()), LineCol { line: 1, col: 2.into() });
45 assert_eq!(index.translate(10.into()), LineCol { line: 1, col: 4.into()}); 51 assert_eq!(index.line_col(10.into()), LineCol { line: 1, col: 4.into() });
46 assert_eq!(index.translate(11.into()), LineCol { line: 1, col: 5.into()}); 52 assert_eq!(index.line_col(11.into()), LineCol { line: 1, col: 5.into() });
47 assert_eq!(index.translate(12.into()), LineCol { line: 1, col: 6.into()}); 53 assert_eq!(index.line_col(12.into()), LineCol { line: 1, col: 6.into() });
48 54
49 let text = "\nhello\nworld"; 55 let text = "\nhello\nworld";
50 let index = LineIndex::new(text); 56 let index = LineIndex::new(text);
51 assert_eq!(index.translate(0.into()), LineCol { line: 0, col: 0.into()}); 57 assert_eq!(index.line_col(0.into()), LineCol { line: 0, col: 0.into() });
52 assert_eq!(index.translate(1.into()), LineCol { line: 1, col: 0.into()}); 58 assert_eq!(index.line_col(1.into()), LineCol { line: 1, col: 0.into() });
53 assert_eq!(index.translate(2.into()), LineCol { line: 1, col: 1.into()}); 59 assert_eq!(index.line_col(2.into()), LineCol { line: 1, col: 1.into() });
54 assert_eq!(index.translate(6.into()), LineCol { line: 1, col: 5.into()}); 60 assert_eq!(index.line_col(6.into()), LineCol { line: 1, col: 5.into() });
55 assert_eq!(index.translate(7.into()), LineCol { line: 2, col: 0.into()}); 61 assert_eq!(index.line_col(7.into()), LineCol { line: 2, col: 0.into() });
56} 62}