Skip to content

Commit 5261517

Browse files
author
Noam Lewis
committed
WIP
1 parent 068735c commit 5261517

File tree

2 files changed

+112
-61
lines changed

2 files changed

+112
-61
lines changed

src/main.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ use ratatui::{
1717
text::{Line, Span, Text},
1818
DefaultTerminal, Frame,
1919
};
20-
use virtual_file::VirtualFile;
20+
use virtual_file::{LineIndex, VirtualFile};
2121

2222
mod lines;
23-
2423
mod memstore;
2524
mod virtual_file;
2625

2726
// TODO
2827
// How to represent edited content?
28+
// How to map offset (or chunk index) -> line index? (e.g. CTRL + HOME / END implementation)
2929
//
3030
// Main problem: "insert char" pushes all remaining memory forward.
3131
//
@@ -45,6 +45,8 @@ struct State {
4545
/// Content loaded from the file, may be a small portion of the entire file starting at some offset
4646
lines: VirtualFile,
4747

48+
line_index: LineIndex,
49+
4850
/// Cursor position relative to ???
4951
cursor: Position,
5052

@@ -240,7 +242,7 @@ impl State {
240242
self.insert_char(c);
241243
return;
242244
}
243-
let line = self.lines.get_mut();
245+
let line = self.lines.get_mut(&self.line_index);
244246
if line.len() < (self.cursor.x as usize) {
245247
line.overwrite(self.cursor.x as usize, c);
246248
self.cursor.x += 1;
@@ -478,14 +480,16 @@ fn main() -> io::Result<()> {
478480
.open(filename)?
479481
};
480482
let terminal = ratatui::init();
483+
let lines = VirtualFile::new(1024 * 1024, file);
481484
let mut state: State = State {
482485
//lines: vec![LoadedLine::empty()],
483486
window_offset: Position::new(0, 0),
484487
cursor: Position::new(0, 0),
485488
insert_mode: true,
486489
status_text: String::new(),
487490
terminal_size: terminal.size()?,
488-
lines: VirtualFile::new(1024 * 1024, file),
491+
line_index: lines.get_index(),
492+
lines,
489493
};
490494
//state.load()?;
491495
let result = state.run(terminal);

src/virtual_file.rs

Lines changed: 104 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::{
55
vec,
66
};
77

8+
use ratatui::symbols::line;
9+
810
use crate::{
911
lines::EditLine,
1012
memstore::{Chunk, ChunkIndex, LoadStore, Memstore},
@@ -37,12 +39,18 @@ impl LoadStore for FileLoadStore {
3739
}
3840
}
3941

42+
#[derive(Debug, Clone)]
43+
pub struct LineIndex {
44+
relative: i64,
45+
offset_version: u64,
46+
}
47+
4048
pub struct VirtualFile {
4149
// configuration
4250
chunk_size: u64,
4351

44-
/// index into chunk_lines
45-
line_index: usize,
52+
line_offset: i64,
53+
offset_version: u64,
4654

4755
// indices of chunks loaded in chunk_lines
4856
loaded_chunks: Range<ChunkIndex>,
@@ -58,7 +66,8 @@ impl VirtualFile {
5866
let chunk_zero = ChunkIndex::from_offset(0, chunk_size);
5967
let mut res = VirtualFile {
6068
chunk_size,
61-
line_index: 0,
69+
offset_version: 0,
70+
line_offset: 0,
6271
loaded_chunks: Range {
6372
start: chunk_zero.clone(),
6473
end: chunk_zero.clone(),
@@ -99,8 +108,9 @@ impl VirtualFile {
99108
} else if new_index.next() == self.loaded_chunks.start && !self.loaded_chunks.is_empty() {
100109
self.loaded_chunks.start = new_index;
101110
// append existing lines to new lines
102-
// line_index is relative to the range start, which was pushed up by the new chunk
103-
self.line_index += new_chunk_lines.len();
111+
// line indexes are relative to the range start, which was pushed up by the new chunk
112+
let len: i64 = new_chunk_lines.len().try_into().unwrap();
113+
self.line_offset = self.line_offset + len;
104114
std::mem::swap(&mut self.chunk_lines, &mut new_chunk_lines);
105115
self.chunk_lines
106116
.last_mut()
@@ -114,64 +124,97 @@ impl VirtualFile {
114124
end: new_index.next(),
115125
};
116126
self.chunk_lines = new_chunk_lines;
117-
self.line_index = 0;
127+
self.line_offset = 0;
128+
self.offset_version += 1;
118129
};
119130
}
120131

121-
pub fn prev_line(&mut self) -> u16 {
122-
if self.line_index == 0 && self.loaded_chunks.start.index > 0 {
132+
pub fn prev_line(&mut self, line_index: &LineIndex) -> Option<LineIndex> {
133+
let index = self.to_abs_index(&line_index);
134+
if index.is_none() {
135+
return None;
136+
}
137+
let index = index.unwrap();
138+
if index == 0 && self.loaded_chunks.start.index > 0 {
123139
// seek to previous chunk
124140
self.seek(self.chunk_size * (self.loaded_chunks.start.index - 1));
141+
assert!(line_index.offset_version == self.offset_version);
125142
}
126-
// after possible seek, line_index may still be zero if there was nothing to load
127-
if self.line_index > 0 {
128-
self.line_index -= 1;
129-
return 1;
143+
// after possible seek, index may still be zero if there was nothing to load
144+
if index > 0 {
145+
return Some(LineIndex {
146+
relative: line_index.relative - 1,
147+
offset_version: line_index.offset_version,
148+
});
130149
}
131-
return 0;
150+
return Some(line_index.clone());
132151
}
133152

134-
pub fn next_line(&mut self) -> u16 {
135-
if self.line_index + 2 >= self.chunk_lines.len() {
153+
pub fn next_line(&mut self, line_index: &LineIndex) -> Option<LineIndex> {
154+
let index = self.to_abs_index(&line_index);
155+
if index.is_none() {
156+
return None;
157+
}
158+
let index = index.unwrap();
159+
if index + 2 >= self.chunk_lines.len() {
136160
// fetch more lines, after increasing index it will be the last line which may be incomplete
137161
self.seek(self.loaded_chunks.end.to_offset());
162+
assert!(line_index.offset_version == self.offset_version);
138163
}
139-
if self.line_index + 1 < self.chunk_lines.len() {
140-
self.line_index += 1;
141-
return 1;
164+
if index + 1 < self.chunk_lines.len() {
165+
return Some(LineIndex {
166+
relative: line_index.relative + 1,
167+
offset_version: line_index.offset_version,
168+
});
142169
}
143-
return 0;
170+
return Some(line_index.clone());
144171
}
145172

146-
pub fn remove(&mut self) -> EditLine {
147-
if self.line_index + 2 >= self.chunk_lines.len() {
173+
pub fn remove(&mut self, line_index: &LineIndex) -> Option<EditLine> {
174+
let index = self.to_abs_index(&line_index);
175+
if index.is_none() {
176+
return None;
177+
}
178+
let index = index.unwrap();
179+
if index + 2 >= self.chunk_lines.len() {
148180
// fetch more lines, after removal it will be the last line which may be incomplete
149181
self.seek(self.loaded_chunks.end.to_offset());
182+
assert!(line_index.offset_version == self.offset_version);
150183
}
151-
let removed_line = self.chunk_lines.remove(self.line_index);
152-
if self.line_index > 0 {
153-
self.line_index -= 1;
154-
} else if self.chunk_lines.len() == 0 {
184+
let removed_line = self.chunk_lines.remove(index);
185+
if self.chunk_lines.len() == 0 {
155186
// that was the only line left, add one back to avoid empty
156187
self.chunk_lines.push(EditLine::empty());
157188
}
158-
return removed_line;
189+
return Some(removed_line);
159190
}
160191

161-
pub fn get_index(&self) -> usize {
162-
self.line_index
163-
}
164-
165-
pub fn insert_after(&mut self, new_line: EditLine) {
166-
self.chunk_lines.insert(self.line_index + 1, new_line);
192+
pub fn insert_after(&mut self, line_index: &LineIndex, new_line: EditLine) -> Option<()> {
193+
match self.to_abs_index(&line_index) {
194+
None => return None,
195+
Some(index) => {
196+
self.chunk_lines.insert(index + 1, new_line);
197+
return Some(());
198+
}
199+
}
167200
}
168201

169-
pub fn get(&self) -> &EditLine {
170-
self.chunk_lines.get(self.line_index).unwrap()
202+
pub fn get(&self, line_index: &LineIndex) -> Option<&EditLine> {
203+
match self.to_abs_index(&line_index) {
204+
None => return None,
205+
Some(index) => {
206+
return self.chunk_lines.get(index);
207+
}
208+
}
171209
}
172210

173-
pub fn get_mut(&mut self) -> &mut EditLine {
174-
self.chunk_lines.get_mut(self.line_index).unwrap()
211+
pub fn get_mut(&mut self, line_index: &LineIndex) -> Option<&mut EditLine> {
212+
match self.to_abs_index(&line_index) {
213+
None => return None,
214+
Some(index) => {
215+
return self.chunk_lines.get_mut(index);
216+
}
217+
}
175218
}
176219

177220
fn parse_chunk(data: &Vec<u8>) -> Vec<EditLine> {
@@ -181,34 +224,38 @@ impl VirtualFile {
181224
.collect()
182225
}
183226

227+
pub fn get_index(&self) -> LineIndex {
228+
LineIndex {
229+
relative: 0,
230+
offset_version: self.offset_version,
231+
}
232+
}
233+
184234
pub fn iter_at(
185235
&mut self,
186-
offset_from_line_index: i64,
236+
line_index: &LineIndex,
187237
count: usize,
188238
) -> impl Iterator<Item = &EditLine> {
189-
// TODO: This is inefficient and also clobbers the current line_index
190-
let mut clobber: i32 = 0;
191-
if offset_from_line_index < 0 {
192-
// need to iterate lines backwards
193-
for _ in offset_from_line_index..0 {
194-
clobber -= self.prev_line() as i32;
239+
match self.to_abs_index(&line_index) {
240+
None => return [].iter(),
241+
Some(index) => {
242+
// materialize 'count' lines
243+
let mut line_index = line_index.clone();
244+
for _ in 0..count {
245+
line_index = self.next_line(&line_index).unwrap();
246+
}
247+
self.chunk_lines[index..(index + count)].iter()
195248
}
196-
} else {
197-
// need to iterate lines forwards
198-
for _ in 0..offset_from_line_index {
199-
clobber += self.next_line() as i32;
200-
}
201-
}
202-
// line_index is now at the start of the range
203-
let start_index = self.line_index;
204-
// materialize 'count' lines
205-
for _ in 0..count {
206-
clobber += self.next_line() as i32;
207249
}
208-
// ... and now go back to where line_index was before
209-
self.line_index = (self.line_index as i32 - clobber).try_into().unwrap();
250+
}
210251

211-
self.chunk_lines.iter().skip(start_index)
252+
fn to_abs_index(&self, line_index: &LineIndex) -> Option<usize> {
253+
if self.offset_version != line_index.offset_version {
254+
return None;
255+
}
256+
let index = (line_index.relative + self.line_offset).try_into().unwrap();
257+
assert!(index < self.chunk_lines.len());
258+
Some(index)
212259
}
213260
}
214261

0 commit comments

Comments
 (0)