diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a8195b9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.associations": { + "array": "c", + "string": "c", + "string_view": "c", + "ranges": "c", + "span": "c", + "*.inc": "c", + "*.ipp": "c", + "bitset": "c", + "format": "c", + "initializer_list": "c", + "vector": "c", + "__bit_reference": "c" + } +} \ No newline at end of file diff --git a/final/binary b/final/binary new file mode 100755 index 0000000..8b4cb0a Binary files /dev/null and b/final/binary differ diff --git a/final/dynstr.c b/final/dynstr.c new file mode 100644 index 0000000..a55a4b9 --- /dev/null +++ b/final/dynstr.c @@ -0,0 +1,232 @@ +/// dynamic array class +/// written by: Harry Irving + +#include +#include +#include +#include + +typedef struct { + int size; + int capacity; + char* data; +} String; + +String str_with_capacity(int capacity) { + String s; + + /// allocate memory for 'capacity' chars + s.data = (char*)calloc(capacity, sizeof(char)); + + s.size = 0; + s.capacity = capacity; + + return s; +} + +String str_from_chars(char* string) { + String s; + + s.data = (char*)calloc(strlen(string), sizeof(char)); + strcpy(s.data, string); + s.size = strlen(string); + s.capacity = strlen(string); + return s; +} + +String str_from_slice(char* string, int len) { + String s; + + s.data = (char*)calloc(len, sizeof(char)); + strncpy(s.data, string, len); + s.size = len; + s.capacity = len; + return s; +} + + +String str_new() { + return str_with_capacity(1); +} + +int str_dealloc(String* self) { + free(self->data); + return 0; +} + +void str_push(String* self, char c) { + // check size < capacity + if (self->size < self->capacity) { + self->data[self->size] = c; + self->size++; + return; + } + + // reallocate to add capacity for an extra char + int newcap = self->capacity + sizeof(char); + self->data = (char*)realloc(self->data, newcap); + self->data[self->size] = c; + self->size++; + self->capacity = newcap; + return; +} + +char str_pop(String* self) { + if (self->size == 0) { + return '\0'; + } + self->size--; + char c = self->data[self->size]; + return c; +} + +void str_insert(String* self, int index, char c) { + if (index > self->size) { + return; + } + self->size++; + for (int i = self->size - 1; i > index; i--) { + self->data[i] = self->data[i - 1]; + } + self->data[index] = c; +} + +void str_remove(String* self, int index) { + if (index >= self->size) { + return; + } + self->size--; + for (int i = index; i < self->size; i++) { + self->data[i] = self->data[i + 1]; + } +} + +String* str_lines(String* self, int* numlines) { + + char* string = self->data; + String* lines = NULL; + + // find the number of lines in the file + *numlines = 0; + for (int i = 0; i < strlen(string); i++) { + if (string[i] == '\n') { + (*numlines)++; + } + } + + // add one if the last char is not a newline + // in this case there is one more line than newline symbols + if (self->data[strlen(self->data) - 1] != '\n') { + (*numlines)++; + } + + // allocate memory for an array of pointers to each line + lines = (String*)malloc((*numlines + 1) * sizeof(String)); + + int i = 0; + char* start = string; + char* end = string; + + while (*end != '\0') { + if (*end == '\n') { + lines[i] = str_from_slice(start, end - start); + + end++; + start = end; + + i++; + } else { + end++; + } + } + // returns an array of String + return lines; +} + +/// splits a string into an array of strings based on a delimiter +String* str_split(String* self, int* res_len, char c) { + + char* string = self->data; + String* elements = NULL; + + // find the number of lines in the file + + bool flag = false; + *res_len = 0; + for (int i = 0; i < strlen(string); i++) { + if (string[i] == c) { + if (flag) { + (*res_len)++; + flag = false; + } + } else { + flag = true; + } + } + if (flag) { + (*res_len)++; + } + + // allocate memory for an array of pointers to each line + elements = (String*)malloc((*res_len) * sizeof(String)); + + int i = 0; + flag = false; + char* start = string; + char* end = string; + + while (*end != '\0') { + if (*end == c) { + if (flag) { + elements[i] = str_from_slice(start, end - start); + i++; + } + end++; + start = end; + flag = false; + } else { + end++; + flag = true; + } + } if (flag) { + elements[i] = str_from_slice(start, end - start); + } + + + // returns an array of String + return elements; +} + +int str_len(String* s) { + return s->size; +} + +char* to_chars(String* s) { + return s->data; +} + +// int main() { +// String s = str_from_chars("hello\nworld\neeeee\notherline\n\0"); + +// int numlines = 0; +// String* lns = str_lines(&s, &numlines); +// str_dealloc(&s); + +// for (int i = 0; i < numlines; i++) { +// printf("%s\n", lns[i].data); +// str_dealloc(&lns[i]); +// } + +// String s2 = str_from_chars("$this$needs$to$be$split"); + +// int elements = 0; +// String* split = str_split(&s2, &elements, '$'); +// str_dealloc(&s2); + +// printf("%d elements\n", elements); +// for (int i = 0; i < elements; i++) { +// printf("%s\n", split[i].data); +// str_dealloc(&split[i]); +// printf("(dealloced)\n"); +// } +// } \ No newline at end of file diff --git a/final/dynstr.h b/final/dynstr.h new file mode 100644 index 0000000..0391223 --- /dev/null +++ b/final/dynstr.h @@ -0,0 +1,36 @@ +#ifndef DYNSTR_H +#define DYNSTR_H + +typedef struct { + int size; + int capacity; + char* data; +} String; + +String str_with_capacity(int capacity); + +String str_from_chars(char* string); + +String str_from_slice(char* string, int len); + +String str_new(); + +String* str_lines(String* self, int* numlines); + +// String* str_split(String* self, int* res_len, char c); + +void str_push(String* s, char c); + +char str_pop(String* s); + +void str_insert(String* s, int index, char c); + +void str_remove(String* s, int index); + +int str_dealloc(String* s); + +char* to_chars(String* s); + +int str_len(String* s); + +#endif diff --git a/final/editor.c b/final/editor.c new file mode 100644 index 0000000..f90b417 --- /dev/null +++ b/final/editor.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include "dynstr.h" +#include + +typedef struct { + int lines; + int screen_line; + int screen_col; + bool editmode; + String* buffer; +} Editor; + +Editor new_editor() { + Editor e; + e.lines = 0; + e.screen_line = 0; + e.screen_col = 0; + e.editmode = false; + e.buffer = NULL; + return e; +} + +Editor editor_from(char* input_string) { + // TODO: fix this function + Editor e; + e.lines = 0; + e.screen_line = 0; + e.screen_col = 0; + e.editmode = false; + e.buffer = NULL; + return e; +} + +void move_cursor(Editor* self, int x, int y) { + if (x != 0 + && self->screen_line + y >= 0 + && self->screen_line + y <= self->lines) + { + self->screen_line += y; + int line_width = str_len(&self->buffer[self->screen_line]); + if (self->screen_col > line_width) { + self->screen_col = line_width; + } + } else if (self->screen_col + x < 0) { + if (self->screen_line - 1 >= 0) { + self->screen_col = str_len(&self->buffer[self->screen_line]); + } + } else if (self->screen_col + x > str_len(&self->buffer[self->screen_line])) { + if (self->screen_line + 1 <= self->lines) { + self->screen_col = 0; + self->screen_line += 1; + } + } else if (x != 0) { + self->screen_col += x; + } + + move(self->screen_line, self->screen_col); +} + +void delchar(Editor* self) { + str_remove(&self->buffer[self->screen_line], self->screen_col); + delch(); +} + +void addchar(Editor* self, char c) { + insch(c); + // insert the character into the string at the given index + if (self->screen_line == self->lines) { + // reallocate self->buffer to be 1 larger + self->lines++; + self->buffer = realloc(self->buffer, sizeof(String) * (self->lines + 1)); + } + str_insert(&self->buffer[self->screen_line], self->screen_col, c); + move_cursor(self, 1, 0); +} + +String* to_string(Editor* self) { + +} + diff --git a/final/editor.h b/final/editor.h new file mode 100644 index 0000000..ec45b4b --- /dev/null +++ b/final/editor.h @@ -0,0 +1,18 @@ +#include "dynstr.h" + +typedef struct { + int screen_line; + int screen_col; + bool editmode; + String* buffer; +} Editor; + +Editor new_editor(); + +Editor editor_from(char* input_string); + +void move_cursor(Editor* self, int x, int y); +void delchar(Editor* self); +void addchar(Editor* self, char c); + +String* to_string(Editor* self); \ No newline at end of file diff --git a/final/main.c b/final/main.c index 688cf3c..865c133 100644 --- a/final/main.c +++ b/final/main.c @@ -1,17 +1,7 @@ #include #include #include -#include -#include -#include - -typedef struct { - int file_line; - int file_col; - int buff_line; - int buff_col; - int editmode; -} EditorData; +#include "editor.h" void help() { printf("Usage:\n"); @@ -23,3 +13,56 @@ void help() { printf(" cmd len // returns the length of the specified file\n"); printf(" cmd log // prints a list of all changes made to the file\n"); } + +int open_editor() { + initscr(); + raw(); + noecho(); + keypad(stdscr, true); + int max_y, max_x; + getmaxyx(stdscr, max_y, max_x); + + move(0, 0); + + Editor editor = new_editor(); + + while (true) { + refresh(); + int c = getch(); + if (editor.editmode) { + switch (c) { + case 27: + editor.editmode = false; + break; + case KEY_BACKSPACE: + delchar(&editor); + break; + case KEY_ENTER: + editor.editmode = false; + break; + default: + addchar(&editor, c); + break; + } + } else { + switch (c) { + case 'q': + return 0; + case 'i': + editor.editmode = true; + break; + case 'w': + // TODO: write function to save the data to a file + break; + default: + break; + } + } + } +} + +int main() { + open_editor(); + return 0; +} + diff --git a/final/util.c b/final/util.c new file mode 100644 index 0000000..e69de29 diff --git a/prototype/something.txt b/prototype/something.txt index 6c041dd..2a99654 100644 --- a/prototype/something.txt +++ b/prototype/something.txt @@ -1 +1,7 @@ -[package] name = "cs_coursework_editor_draft" version = "0.1.0" edition = "2021" [dependencies] ncurses = "6.0.1" i may have made a very scuffed version of vim. \ No newline at end of file +[package] +name = "cs_coursework_editor_draft" +version = "0.1.0" +edition = "2021" + +[dependencies] +ncurses = "6.0.1" diff --git a/prototype/src/main.rs b/prototype/src/main.rs index 1d1de9b..51ed803 100644 --- a/prototype/src/main.rs +++ b/prototype/src/main.rs @@ -17,6 +17,7 @@ struct EditorData { } impl EditorData { + /// called when the user creates a new file or supplies a valid file path that does not exist/* */. fn new() -> EditorData { EditorData { buffer: Vec::new(), @@ -27,6 +28,7 @@ impl EditorData { editmode: false, } } + /// called when the user opens an existing file from a valid directory. fn from(s: String) -> EditorData { EditorData { buffer: s @@ -41,22 +43,23 @@ impl EditorData { } } + /// moves the cursor (with bounds checks) fn mv_cursor(&mut self, dy: i32, dx: i32) { if dy != 0 && self.screen_line + dy >= 0 && self.screen_line + dy <= self.buffer.len() as i32 { self.screen_line += dy; - let line_width = self.buffer[self.screen_line as usize].len() as i32; + let line_width = self.buffer.get(self.screen_line as usize).unwrap_or(&Vec::::new()).len() as i32; if self.screen_col > line_width { self.screen_col = line_width; } } else if self.screen_col + dx < 0 { if self.screen_line - 1 >= 0 { self.screen_line -= 1; - self.screen_col = self.buffer[self.screen_line as usize].len() as i32; + self.screen_col = self.buffer.get(self.screen_line as usize).unwrap_or(&Vec::::new()).len() as i32; } - } else if self.screen_col + dx > self.buffer[self.screen_line as usize].len() as i32 { + } else if self.screen_col + dx > self.buffer.get(self.screen_line as usize).unwrap_or(&Vec::::new()).len() as i32 { if self.screen_line + 1 <= self.buffer.len() as i32 { self.screen_col = 0; self.screen_line += 1; @@ -67,6 +70,9 @@ impl EditorData { ncurses::mv(self.screen_line, self.screen_col); } + /// Delete the character at the cursor. + /// if the cursor is at the end of the line, the next line is appended to the current line and the next line is removed. + /// Otherwise, the character at the cursor is removed and the rest of the line is shifted left. fn delchar(&mut self) { if self.screen_col == self.buffer[self.screen_line as usize].len() as i32 { let oldline = self.buffer[1 + self.screen_line as usize].clone(); @@ -76,18 +82,29 @@ impl EditorData { ncurses::clear(); let s = self.to_string(); ncurses::addstr(&s).unwrap(); + ncurses::mv(self.screen_line, self.screen_col); + } else { + self.buffer[self.screen_line as usize].remove(self.screen_col as usize); + ncurses::delch(); } - - self.buffer[self.file_line as usize].remove(self.file_col as usize); - ncurses::delch(); } + /// Insert a character at the cursor, + /// then update the cursor position. fn addchar(&mut self, c: char) { - self.buffer[self.screen_line as usize].insert(self.screen_col as usize, c); + ncurses::insch(keystroke as u32); + if let Some(line) = self.buffer.get_mut(self.screen_line as usize) { + line.insert(self.screen_col as usize, c); + } else { + self.buffer.push(Vec::new()); + self.buffer.get_mut(self.screen_line as usize).unwrap().push(c); + } + data.mv_cursor(0, 1); } } impl ToString for EditorData { + /// converts the buffer to a string so that it can be written back to the text file. fn to_string(&self) -> String { self.buffer .iter() @@ -97,6 +114,18 @@ impl ToString for EditorData { } } +/// commands: +/// +/// * `open `: opens the specified file in the editor. +/// * `rm `: deletes the specified file. +/// * `new `: creates a new empty file at the specified path. +/// * `mv `: moves the specified file to the new path. +/// * `cp `: copies the specified file to the new path. +/// * `lenc `: prints the length of the specified file in characters. +/// * `lenl `: prints the length of the specified file in lines. +/// * `log`: prints a list of all changes made to the file. +/// +/// an invalid command will result in the help message being printed. fn main() { let args = env::args().collect::>(); @@ -136,7 +165,6 @@ fn help() { println!( "Usage: cmd open // opens the specified file -Write cmd rm // deletes the specified file cmd new // creates a new empty file at the specified path cmd mv // moves the specified file to the new path @@ -188,11 +216,11 @@ fn open(filename: &str) -> Result<(), &'static str> { ncurses::KEY_RIGHT => data.mv_cursor(0, 1), ncurses::KEY_UP => data.mv_cursor(-1, 0), ncurses::KEY_DOWN => data.mv_cursor(1, 0), + ncurses::KEY_ENTER => { + + } _ => { - ncurses::insch(keystroke as u32); - data.addchar(char::from_u32(keystroke as u32).unwrap()); - data.mv_cursor(0, 1); } } } else { @@ -202,13 +230,10 @@ fn open(filename: &str) -> Result<(), &'static str> { } 119 => { let buff_size = max_y * max_x; - let mut buff = vec![0; buff_size as usize]; + + + let buff = data.to_string(); - for i in 0..buff_size { - buff[i as usize] = - (ncurses::mvwinch(ncurses::stdscr(), i / max_x, i % max_x) - & ncurses::A_CHARTEXT) as u8; - } ncurses::endwin(); OpenOptions::new() @@ -216,7 +241,7 @@ fn open(filename: &str) -> Result<(), &'static str> { .truncate(true) .open(filename) .unwrap() - .write_all(&buff) + .write_all(buff.as_bytes()) .unwrap(); } 113 => {