summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AK/AKString.h (renamed from AK/String.h)0
-rw-r--r--AK/Bitmap.h2
-rw-r--r--AK/Buffer.h2
-rw-r--r--AK/ByteBuffer.h2
-rw-r--r--AK/DoublyLinkedList.h2
-rw-r--r--AK/FileSystemPath.h2
-rw-r--r--AK/Function.h2
-rw-r--r--AK/HashMap.h2
-rw-r--r--AK/HashTable.h2
-rw-r--r--AK/Makefile2
-rw-r--r--AK/MappedFile.h2
-rw-r--r--AK/Noncopyable.h6
-rw-r--r--AK/OwnPtr.h2
-rw-r--r--AK/StdLibExtras.h (renamed from AK/StdLib.h)0
-rw-r--r--AK/String.cpp4
-rw-r--r--AK/StringBuilder.h2
-rw-r--r--AK/StringImpl.cpp2
-rw-r--r--AK/TemporaryFile.h2
-rw-r--r--AK/test.cpp2
-rw-r--r--Editor/Document.cpp46
-rw-r--r--Editor/Document.h27
-rw-r--r--Editor/Editor.cpp336
-rw-r--r--Editor/Editor.h74
-rw-r--r--Editor/FileReader.cpp40
-rw-r--r--Editor/FileReader.h17
-rw-r--r--Editor/InsertOperation.cpp26
-rw-r--r--Editor/InsertOperation.h17
-rw-r--r--Editor/Line.cpp95
-rw-r--r--Editor/Line.h40
-rw-r--r--Editor/Makefile25
-rw-r--r--Editor/Operation.cpp9
-rw-r--r--Editor/Operation.h14
-rw-r--r--Editor/Position.h27
-rw-r--r--Editor/UndoStack.cpp14
-rw-r--r--Editor/UndoStack.h18
-rw-r--r--Editor/cuki.h6
-rw-r--r--Editor/main.cpp19
-rw-r--r--Kernel/ELFImage.h2
-rw-r--r--Kernel/FIFO.cpp2
-rw-r--r--Kernel/MemoryManager.h2
-rw-r--r--Kernel/Process.cpp2
-rw-r--r--Kernel/Process.h2
-rw-r--r--Kernel/VirtualConsole.cpp2
-rw-r--r--Kernel/system.h2
-rw-r--r--LibC/grp.cpp2
-rw-r--r--LibC/pwd.cpp2
-rw-r--r--LibC/termcap.cpp2
-rw-r--r--Userland/kill.cpp2
-rw-r--r--Userland/ls.cpp2
-rw-r--r--Userland/sleep.cpp2
-rw-r--r--VirtualFileSystem/Ext2FileSystem.cpp2
-rw-r--r--VirtualFileSystem/FileBackedDiskDevice.h2
-rw-r--r--VirtualFileSystem/FileSystem.h2
-rw-r--r--VirtualFileSystem/FullDevice.cpp2
-rw-r--r--VirtualFileSystem/NullDevice.cpp2
-rw-r--r--VirtualFileSystem/RandomDevice.cpp2
-rw-r--r--VirtualFileSystem/SyntheticFileSystem.cpp2
-rw-r--r--VirtualFileSystem/VirtualFileSystem.h2
-rw-r--r--VirtualFileSystem/ZeroDevice.cpp2
59 files changed, 895 insertions, 39 deletions
diff --git a/AK/String.h b/AK/AKString.h
index a8e7e07818..a8e7e07818 100644
--- a/AK/String.h
+++ b/AK/AKString.h
diff --git a/AK/Bitmap.h b/AK/Bitmap.h
index c2f6993f76..cd89ff2996 100644
--- a/AK/Bitmap.h
+++ b/AK/Bitmap.h
@@ -1,6 +1,6 @@
#pragma once
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "Types.h"
#include "kmalloc.h"
#include "Assertions.h"
diff --git a/AK/Buffer.h b/AK/Buffer.h
index efed0313b4..2010bf1613 100644
--- a/AK/Buffer.h
+++ b/AK/Buffer.h
@@ -3,7 +3,7 @@
#include "Assertions.h"
#include "Retainable.h"
#include "RetainPtr.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "kmalloc.h"
namespace AK {
diff --git a/AK/ByteBuffer.h b/AK/ByteBuffer.h
index c576ad2b74..b005deefe7 100644
--- a/AK/ByteBuffer.h
+++ b/AK/ByteBuffer.h
@@ -2,7 +2,7 @@
#include "Buffer.h"
#include "Types.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
namespace AK {
diff --git a/AK/DoublyLinkedList.h b/AK/DoublyLinkedList.h
index 60288ef262..6c7f8dd6b5 100644
--- a/AK/DoublyLinkedList.h
+++ b/AK/DoublyLinkedList.h
@@ -1,6 +1,6 @@
#pragma once
-#include "StdLib.h"
+#include "StdLibExtras.h"
namespace AK {
diff --git a/AK/FileSystemPath.h b/AK/FileSystemPath.h
index 980f836226..d56625dcaf 100644
--- a/AK/FileSystemPath.h
+++ b/AK/FileSystemPath.h
@@ -1,6 +1,6 @@
#pragma once
-#include "String.h"
+#include "AKString.h"
namespace AK {
diff --git a/AK/Function.h b/AK/Function.h
index fe50180517..d1aab89d45 100644
--- a/AK/Function.h
+++ b/AK/Function.h
@@ -27,7 +27,7 @@
#include "Assertions.h"
#include "OwnPtr.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
namespace AK {
diff --git a/AK/HashMap.h b/AK/HashMap.h
index 0035732a16..b1a68abb5b 100644
--- a/AK/HashMap.h
+++ b/AK/HashMap.h
@@ -1,7 +1,7 @@
#pragma once
#include "HashTable.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "kstdio.h"
namespace AK {
diff --git a/AK/HashTable.h b/AK/HashTable.h
index 91df234be4..68e2c164a2 100644
--- a/AK/HashTable.h
+++ b/AK/HashTable.h
@@ -3,7 +3,7 @@
#include "Assertions.h"
#include "DoublyLinkedList.h"
#include "Traits.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "kstdio.h"
//#define HASHTABLE_DEBUG
diff --git a/AK/Makefile b/AK/Makefile
index b706a03eef..1d6353827c 100644
--- a/AK/Makefile
+++ b/AK/Makefile
@@ -5,7 +5,7 @@ CXXFLAGS = -std=c++17 -O0 -W -Wall -ggdb3
all: $(PROGRAM)
-test.o: Vector.h String.h StringImpl.h MappedFile.h HashTable.h SinglyLinkedList.h Traits.h HashMap.h TemporaryFile.h Buffer.h FileSystemPath.h StringBuilder.h
+test.o: Vector.h AKString.h StringImpl.h MappedFile.h HashTable.h SinglyLinkedList.h Traits.h HashMap.h TemporaryFile.h Buffer.h FileSystemPath.h StringBuilder.h
.cpp.o:
$(CXX) $(CXXFLAGS) -o $@ -c $<
diff --git a/AK/MappedFile.h b/AK/MappedFile.h
index d93908c854..4fbc1872dd 100644
--- a/AK/MappedFile.h
+++ b/AK/MappedFile.h
@@ -1,6 +1,6 @@
#pragma once
-#include "String.h"
+#include "AKString.h"
namespace AK {
diff --git a/AK/Noncopyable.h b/AK/Noncopyable.h
new file mode 100644
index 0000000000..ff7df2346b
--- /dev/null
+++ b/AK/Noncopyable.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#define AK_MAKE_NONCOPYABLE(c) \
+private: \
+ c(const c&) = delete; \
+ c& operator=(const c&) = delete;
diff --git a/AK/OwnPtr.h b/AK/OwnPtr.h
index 24c92642a0..c6b55cdb3a 100644
--- a/AK/OwnPtr.h
+++ b/AK/OwnPtr.h
@@ -1,6 +1,6 @@
#pragma once
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "Types.h"
#include "Traits.h"
diff --git a/AK/StdLib.h b/AK/StdLibExtras.h
index 64892b041d..64892b041d 100644
--- a/AK/StdLib.h
+++ b/AK/StdLibExtras.h
diff --git a/AK/String.cpp b/AK/String.cpp
index bf8339e449..fd4b34f06e 100644
--- a/AK/String.cpp
+++ b/AK/String.cpp
@@ -1,5 +1,5 @@
-#include "String.h"
-#include "StdLib.h"
+#include "AKString.h"
+#include "StdLibExtras.h"
namespace AK {
diff --git a/AK/StringBuilder.h b/AK/StringBuilder.h
index 2eb8b1880e..ca38cc26c2 100644
--- a/AK/StringBuilder.h
+++ b/AK/StringBuilder.h
@@ -1,6 +1,6 @@
#pragma once
-#include "String.h"
+#include "AKString.h"
#include "Vector.h"
namespace AK {
diff --git a/AK/StringImpl.cpp b/AK/StringImpl.cpp
index 8fbf11f692..53719e3549 100644
--- a/AK/StringImpl.cpp
+++ b/AK/StringImpl.cpp
@@ -1,5 +1,5 @@
#include "StringImpl.h"
-#include "StdLib.h"
+#include "StdLibExtras.h"
#include "kmalloc.h"
namespace AK {
diff --git a/AK/TemporaryFile.h b/AK/TemporaryFile.h
index bcc88b7652..4596a214d0 100644
--- a/AK/TemporaryFile.h
+++ b/AK/TemporaryFile.h
@@ -1,6 +1,6 @@
#pragma once
-#include "String.h"
+#include "AKString.h"
#include <stdio.h>
namespace AK {
diff --git a/AK/test.cpp b/AK/test.cpp
index 5d2bcbca14..49f057c760 100644
--- a/AK/test.cpp
+++ b/AK/test.cpp
@@ -1,4 +1,4 @@
-#include "String.h"
+#include "AKString.h"
//#include "StringBuilder.h"
#include "Vector.h"
#include <stdio.h>
diff --git a/Editor/Document.cpp b/Editor/Document.cpp
new file mode 100644
index 0000000000..26d3b24f54
--- /dev/null
+++ b/Editor/Document.cpp
@@ -0,0 +1,46 @@
+#include "Document.h"
+#include "FileReader.h"
+
+OwnPtr<Document> Document::create_from_file(const std::string& path)
+{
+ auto document = make<Document>();
+
+ FileReader reader(path);
+ while (reader.can_read()) {
+ auto line = reader.read_line();
+ document->m_lines.push_back(Line(line));
+ }
+
+ return document;
+}
+
+void Document::dump()
+{
+ fprintf(stderr, "Document{%p}\n", this);
+ for (size_t i = 0; i < m_lines.size(); ++i) {
+ fprintf(stderr, "[%02zu] %s\n", i, m_lines[i].data().c_str());
+ }
+}
+
+bool Document::backspace_at(Position position)
+{
+ return false;
+}
+
+bool Document::insert_at(Position position, const std::string& text)
+{
+ static FILE* f = fopen("log", "a");
+ fprintf(f, "@%zu,%zu: +%s\n", position.line(), position.column(), text.c_str());
+ fflush(f);
+ ASSERT(position.is_valid());
+ if (!position.is_valid())
+ return false;
+ ASSERT(position.line() < line_count());
+ if (position.line() >= line_count())
+ return false;
+ Line& line = m_lines[position.line()];
+ if (position.column() > line.length())
+ return false;
+ line.insert(position.column(), text);
+ return true;
+}
diff --git a/Editor/Document.h b/Editor/Document.h
new file mode 100644
index 0000000000..9243038180
--- /dev/null
+++ b/Editor/Document.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "cuki.h"
+#include "Line.h"
+#include "Position.h"
+#include "OwnPtr.h"
+#include <string>
+
+class Document {
+public:
+ Document() { }
+ ~Document() { }
+
+ const std::deque<Line>& lines() const { return m_lines; }
+ std::deque<Line>& lines() { return m_lines; }
+ size_t line_count() const { return m_lines.size(); }
+
+ static OwnPtr<Document> create_from_file(const std::string& path);
+
+ bool insert_at(Position, const std::string&);
+ bool backspace_at(Position);
+
+ void dump();
+
+private:
+ std::deque<Line> m_lines;
+};
diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp
new file mode 100644
index 0000000000..0ad3b3edd0
--- /dev/null
+++ b/Editor/Editor.cpp
@@ -0,0 +1,336 @@
+#include "Editor.h"
+#include "Document.h"
+#include "InsertOperation.h"
+
+#define _XOPEN_SOURCE_EXTENDED
+#include <locale.h>
+#include <ncurses.h>
+
+static int statusbar_attributes;
+static int ruler_attributes;
+
+Editor::Editor()
+{
+ setlocale(LC_ALL, "");
+ initscr();
+ start_color();
+ use_default_colors();
+
+ init_pair(1, COLOR_WHITE, COLOR_BLUE);
+ init_pair(2, COLOR_BLUE, -1);
+
+ statusbar_attributes = COLOR_PAIR(1);
+ ruler_attributes = COLOR_PAIR(2);
+
+ raw();
+ keypad(stdscr, true);
+ timeout(10);
+ noecho();
+ refresh();
+}
+
+Editor::~Editor()
+{
+ //move(2, 2);
+ //printw("*** Press any key to exit! ***");
+ //getch();
+ endwin();
+}
+
+void Editor::set_document(OwnPtr<Document>&& document)
+{
+ m_document = std::move(document);
+ m_cursor.move_to(0, 0);
+ m_scroll_position.move_to(0, 0);
+}
+
+void Editor::redraw()
+{
+ clear();
+ if (!m_document)
+ return;
+
+ size_t window_height = getmaxy(stdscr);
+ size_t window_width = getmaxx(stdscr);
+
+ for (size_t row = 0; row < window_height - 1; ++row) {
+ size_t current_document_line = m_scroll_position.line() + row;
+ size_t current_document_column = m_scroll_position.column();
+
+ move(row, 0);
+
+ if (current_document_line >= m_document->line_count()) {
+ printw("~");
+ } else {
+ attron(ruler_attributes);
+ printw("%3d ", current_document_line);
+ attroff(ruler_attributes);
+ m_ruler_width = 4;
+ size_t line_length = m_document->lines()[current_document_line].data().size();
+ const char* line_data = m_document->lines()[current_document_line].data().c_str();
+ if (m_scroll_position.column() < line_length)
+ addnstr(line_data + m_scroll_position.column(), window_width - m_ruler_width);
+ }
+ }
+
+ draw_status_bar();
+
+ draw_cursor();
+ refresh();
+}
+
+void Editor::draw_cursor()
+{
+ ssize_t cursor_row_on_screen = m_cursor.line() - m_scroll_position.line();
+ ssize_t cursor_column_on_screen = m_cursor.column() - m_scroll_position.column();
+
+ move(cursor_row_on_screen, cursor_column_on_screen + m_ruler_width);
+
+}
+
+void Editor::draw_status_bar()
+{
+ int old_background = getbkgd(stdscr);
+ bkgdset(' ' | statusbar_attributes);
+
+ size_t window_height = getmaxy(stdscr);
+ size_t window_width = getmaxx(stdscr);
+
+ move(window_height - 1, 0);
+ clrtoeol();
+
+ if (is_editing_document()) {
+ attron(A_STANDOUT);
+ printw("* Editing *");
+ attroff(A_STANDOUT);
+ } else if (is_editing_command()) {
+ printw("\\%s", m_command.c_str());
+ } else {
+ attron(A_BOLD);
+ addstr("~(^_^)~ ");
+ if (m_status_text.size() > 0) {
+ addstr(m_status_text.c_str());
+ }
+ attroff(A_BOLD);
+ }
+
+ move(window_height - 1, window_width - 20);
+ printw("%zu, %zu", m_scroll_position.line(), m_scroll_position.column());
+
+ move(window_height - 1, window_width - 8);
+ printw("%zu, %zu", m_cursor.line(), m_cursor.column());
+ attroff(statusbar_attributes);
+
+ bkgdset(old_background);
+}
+
+int Editor::exec()
+{
+ while (!m_should_quit) {
+ redraw();
+ int ch = getch();
+ if (ch == ERR) {
+ continue;
+ fprintf(stderr, "getch() returned ERR\n");
+ break;
+ }
+
+ if (is_editing_document() || is_editing_command()) {
+ if (ch == 27)
+ set_mode(Idle);
+ else {
+ if (is_editing_document())
+ handle_document_key_press(ch);
+ else
+ handle_command_key_press(ch);
+ }
+ } else {
+ switch (ch) {
+ case 'h': move_left(); break;
+ case 'j': move_down(); break;
+ case 'k': move_up(); break;
+ case 'l': move_right(); break;
+ case 'i': set_mode(EditingDocument); break;
+ case 'q': m_should_quit = true; break;
+ case '\\': set_mode(EditingCommand); break;
+ }
+ }
+ }
+ return 0;
+}
+
+void Editor::move_left()
+{
+ if (m_cursor.column() == 0)
+ return;
+ m_cursor.move_by(0, -1);
+ update_scroll_position_if_needed();
+}
+
+void Editor::move_down()
+{
+ if (m_cursor.line() >= max_line())
+ return;
+ m_cursor.move_by(1, 0);
+ if (m_cursor.column() > max_column())
+ m_cursor.set_column(max_column());
+
+ update_scroll_position_if_needed();
+}
+
+void Editor::move_up()
+{
+ if (m_cursor.line() == 0)
+ return;
+ m_cursor.move_by(-1, 0);
+ if (m_cursor.column() > max_column())
+ m_cursor.set_column(max_column());
+
+ update_scroll_position_if_needed();
+}
+
+void Editor::move_right()
+{
+ if (m_cursor.column() >= max_column())
+ return;
+ m_cursor.move_by(0, 1);
+ update_scroll_position_if_needed();
+}
+
+size_t Editor::max_line() const
+{
+ return m_document->line_count() - 1;
+}
+
+size_t Editor::max_column() const
+{
+ return m_document->lines()[m_cursor.line()].data().size();
+}
+
+void Editor::update_scroll_position_if_needed()
+{
+ ssize_t max_row = getmaxy(stdscr) - 2;
+ ssize_t max_column = getmaxx(stdscr) - 1 - m_ruler_width;
+
+ ssize_t cursor_row_on_screen = m_cursor.line() - m_scroll_position.line();
+ ssize_t cursor_column_on_screen = m_cursor.column() - m_scroll_position.column();
+
+ // FIXME: Need to move by more than just 1 step sometimes!
+
+ if (cursor_row_on_screen < 0) {
+ m_scroll_position.move_by(-1, 0);
+ }
+
+ if (cursor_row_on_screen > max_row) {
+ m_scroll_position.move_by(1, 0);
+ }
+
+ if (cursor_column_on_screen < 0) {
+ m_scroll_position.move_by(0, -1);
+ }
+
+ if (cursor_column_on_screen > max_column) {
+ m_scroll_position.move_by(0, 1);
+ }
+}
+
+void Editor::set_mode(Mode m)
+{
+ if (m_mode == m)
+ return;
+ m_mode = m;
+ m_command = "";
+}
+
+static bool is_backspace(int ch)
+{
+ return ch == 8 || ch == 127;
+}
+
+static bool is_newline(int ch)
+{
+ return ch == 10 || ch == 13;
+}
+
+void Editor::handle_command_key_press(int ch)
+{
+ if (is_backspace(ch)) {
+ if (m_command.size() > 0)
+ m_command.pop_back();
+ else
+ set_mode(Idle);
+ return;
+ }
+ if (is_newline(ch)) {
+ if (m_command.size() > 0)
+ exec_command();
+ set_mode(Idle);
+ return;
+ }
+ m_command.push_back(ch);
+}
+
+void Editor::handle_document_key_press(int ch)
+{
+ if (is_backspace(ch)) {
+ //auto op = make<EraseOperation>(1);
+ m_document->backspace_at(m_cursor);
+ } else {
+ auto op = make<InsertOperation>(ch);
+ run(std::move(op));
+ }
+}
+
+void Editor::run(OwnPtr<Operation>&& op)
+{
+ ASSERT(op);
+ op->apply(*this);
+ m_undo_stack.push(std::move(op));
+}
+
+void Editor::insert_at_cursor(int ch)
+{
+ std::string s;
+ s += ch;
+ m_document->insert_at(m_cursor, s);
+ m_cursor.move_by(0, 1);
+}
+
+bool Editor::insert_text_at_cursor(const std::string& text)
+{
+ ASSERT(text.size() == 1);
+ m_document->insert_at(m_cursor, text);
+ m_cursor.move_by(0, text.size());
+ return true;
+}
+
+bool Editor::remove_text_at_cursor(const std::string& text)
+{
+ // FIXME: Implement
+ ASSERT(false);
+ return false;
+}
+
+void Editor::set_status_text(const std::string& text)
+{
+ m_status_text = text;
+}
+
+void Editor::exec_command()
+{
+ if (m_command == "q") {
+ m_should_quit = true;
+ return;
+ }
+
+ if (m_command == "about") {
+ set_status_text("cuki editor!");
+ return;
+ }
+
+ std::string s;
+ s = "Invalid command: '";
+ s += m_command;
+ s += "'";
+ set_status_text(s);
+}
diff --git a/Editor/Editor.h b/Editor/Editor.h
new file mode 100644
index 0000000000..ec1f53ff11
--- /dev/null
+++ b/Editor/Editor.h
@@ -0,0 +1,74 @@
+#pragma once
+
+#include "OwnPtr.h"
+#include "Position.h"
+#include "UndoStack.h"
+#include <string>
+
+class Document;
+
+class Editor {
+public:
+ Editor();
+ ~Editor();
+
+ void set_document(OwnPtr<Document>&&);
+ void redraw();
+
+ int exec();
+
+ enum Mode {
+ Idle,
+ EditingCommand,
+ EditingDocument,
+ };
+
+ void set_mode(Mode);
+ Mode mode() const { return m_mode; }
+ bool is_editing_document() const { return m_mode == EditingDocument; }
+ bool is_editing_command() const { return m_mode == EditingCommand; }
+ bool is_idle() const { return m_mode == Idle; }
+
+ void set_status_text(const std::string&);
+
+ bool insert_text_at_cursor(const std::string&);
+ bool remove_text_at_cursor(const std::string&);
+
+ void run(OwnPtr<Operation>&&);
+
+private:
+ void move_left();
+ void move_down();
+ void move_up();
+ void move_right();
+
+ size_t max_line() const;
+ size_t max_column() const;
+
+ void update_scroll_position_if_needed();
+
+ void draw_status_bar();
+ void draw_cursor();
+
+ void handle_document_key_press(int ch);
+ void handle_command_key_press(int ch);
+
+ void insert_at_cursor(int ch);
+
+ void exec_command();
+
+ OwnPtr<Document> m_document;
+
+ UndoStack m_undo_stack;
+
+ // Document relative
+ Position m_scroll_position;
+ Position m_cursor;
+
+ std::string m_command;
+ std::string m_status_text;
+
+ bool m_should_quit { false };
+ size_t m_ruler_width { 0 };
+ Mode m_mode { Idle };
+};
diff --git a/Editor/FileReader.cpp b/Editor/FileReader.cpp
new file mode 100644
index 0000000000..0788d06b02
--- /dev/null
+++ b/Editor/FileReader.cpp
@@ -0,0 +1,40 @@
+#include "FileReader.h"
+
+FileReader::FileReader(const std::string& path)
+ : m_path(path)
+{
+ m_file = fopen(path.c_str(), "r");
+}
+
+FileReader::~FileReader()
+{
+ if (m_file)
+ fclose(m_file);
+ m_file = nullptr;
+}
+
+bool FileReader::can_read() const
+{
+ return m_file && !feof(m_file);
+}
+
+std::string FileReader::read_line()
+{
+ if (!m_file) {
+ fprintf(stderr, "Error: FileReader::readLine() called on invalid FileReader for '%s'\n", m_path.c_str());
+ return std::string();
+ }
+
+ std::string line;
+
+ while (can_read()) {
+ int ch = fgetc(m_file);
+ if (ch == EOF)
+ break;
+ if (ch == '\n')
+ break;
+ line += ch;
+ }
+
+ return line;
+}
diff --git a/Editor/FileReader.h b/Editor/FileReader.h
new file mode 100644
index 0000000000..54fde0780e
--- /dev/null
+++ b/Editor/FileReader.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <string>
+#include <stdio.h>
+
+class FileReader {
+public:
+ explicit FileReader(const std::string& path);
+ ~FileReader();
+
+ bool can_read() const;
+ std::string read_line();
+
+private:
+ std::string m_path;
+ FILE* m_file { nullptr };
+};
diff --git a/Editor/InsertOperation.cpp b/Editor/InsertOperation.cpp
new file mode 100644
index 0000000000..cd7de9956b
--- /dev/null
+++ b/Editor/InsertOperation.cpp
@@ -0,0 +1,26 @@
+#include "InsertOperation.h"
+#include "Editor.h"
+
+InsertOperation::InsertOperation(const std::string& text)
+ : m_text(text)
+{
+}
+
+InsertOperation::InsertOperation(char ch)
+ : m_text(&ch, 1)
+{
+}
+
+InsertOperation::~InsertOperation()
+{
+}
+
+bool InsertOperation::apply(Editor& editor)
+{
+ return editor.insert_text_at_cursor(m_text);
+}
+
+bool InsertOperation::unapply(Editor& editor)
+{
+ return editor.remove_text_at_cursor(m_text);
+}
diff --git a/Editor/InsertOperation.h b/Editor/InsertOperation.h
new file mode 100644
index 0000000000..97096ed312
--- /dev/null
+++ b/Editor/InsertOperation.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Operation.h"
+#include <string>
+
+class InsertOperation final : public Operation {
+public:
+ explicit InsertOperation(const std::string&);
+ explicit InsertOperation(char);
+ ~InsertOperation();
+
+ virtual bool apply(Editor&) override;
+ virtual bool unapply(Editor&) override;
+
+private:
+ std::string m_text;
+};
diff --git a/Editor/Line.cpp b/Editor/Line.cpp
new file mode 100644
index 0000000000..431bf1d04f
--- /dev/null
+++ b/Editor/Line.cpp
@@ -0,0 +1,95 @@
+#include "Line.h"
+
+Chunk::Chunk(const std::string& str)
+ : m_data(str)
+{
+}
+
+Chunk::~Chunk()
+{
+}
+
+Line::Line(const std::string& str)
+{
+ m_chunks.push_back(Chunk(str));
+}
+
+Line::Line(Line&& other)
+ : m_chunks(std::move(other.m_chunks))
+{
+}
+
+Line::~Line()
+{
+}
+
+std::string Line::data() const
+{
+ std::string str;
+ for (auto& chunk : m_chunks)
+ str += chunk.data();
+ return str;
+}
+
+void Line::append(const std::string& text)
+{
+ m_chunks.push_back(Chunk(text));
+}
+
+void Line::prepend(const std::string& text)
+{
+ m_chunks.push_front(Chunk(text));
+}
+
+void Line::insert(size_t index, const std::string& text)
+{
+ if (index == 0) {
+ prepend(text);
+ return;
+ }
+
+ if (index == length()) {
+ append(text);
+ return;
+ }
+
+ auto chunkAddress = chunkIndexForPosition(index);
+ auto chunkIndex = std::get<0>(chunkAddress);
+ auto& chunk = m_chunks[chunkIndex];
+ auto indexInChunk = std::get<1>(chunkAddress);
+
+ static FILE* f = fopen("log", "a");
+ fprintf(f, "#Column:%zu, Chunk:%zu, Index:%zu\n", index, chunkIndex, indexInChunk);
+
+ auto leftString = chunk.data().substr(0, indexInChunk);
+ auto rightString = chunk.data().substr(indexInChunk, chunk.length() - indexInChunk);
+
+ fprintf(f, "#{\"%s\", \"%s\", \"%s\"}\n", leftString.c_str(), text.c_str(), rightString.c_str());
+ fflush(f);
+
+ Chunk leftChunk { leftString };
+ Chunk midChunk { text };
+ Chunk rightChunk { rightString };
+
+ auto iterator = m_chunks.begin() + chunkIndex;
+ m_chunks.erase(iterator);
+ iterator = m_chunks.begin() + chunkIndex;
+
+ // Note reverse insertion order!
+ m_chunks.insert(iterator, rightChunk);
+ m_chunks.insert(iterator, midChunk);
+ m_chunks.insert(iterator, leftChunk);
+}
+
+std::tuple<size_t, size_t> Line::chunkIndexForPosition(size_t position)
+{
+ ASSERT(position < length());
+ size_t seen { 0 };
+ for (size_t i = 0; i < m_chunks.size(); ++i) {
+ if (position < seen + m_chunks[i].length())
+ return std::make_tuple(i, position - seen);
+ seen += m_chunks[i].length();
+ }
+ ASSERT(false);
+ return std::make_tuple(0, 0);
+}
diff --git a/Editor/Line.h b/Editor/Line.h
new file mode 100644
index 0000000000..d8e65fc243
--- /dev/null
+++ b/Editor/Line.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "cuki.h"
+#include <deque>
+#include <string>
+#include <tuple>
+
+class Chunk {
+public:
+ explicit Chunk(const std::string&);
+ ~Chunk();
+
+ const std::string& data() const { return m_data; }
+ size_t length() const { return m_data.size(); }
+
+private:
+ std::string m_data;
+};
+
+class Line {
+ AK_MAKE_NONCOPYABLE(Line);
+public:
+ Line() { }
+ Line(const std::string&);
+ Line(Line&&);
+ ~Line();
+
+ std::string data() const;
+ size_t length() const { return data().size(); }
+
+ void insert(size_t index, const std::string&);
+
+private:
+ void append(const std::string&);
+ void prepend(const std::string&);
+
+ std::tuple<size_t, size_t> chunkIndexForPosition(size_t);
+
+ std::deque<Chunk> m_chunks;
+};
diff --git a/Editor/Makefile b/Editor/Makefile
new file mode 100644
index 0000000000..b67c24746c
--- /dev/null
+++ b/Editor/Makefile
@@ -0,0 +1,25 @@
+BINARY = cuki
+
+OBJS = \
+ Document.o \
+ Line.o \
+ FileReader.o \
+ Editor.o \
+ main.o \
+ InsertOperation.o \
+ Operation.o \
+ UndoStack.o
+
+CXXFLAGS = -Os -std=c++1z -W -Wall -g -I../AK
+LDFLAGS = -lncurses
+
+$(BINARY): $(OBJS)
+ $(CXX) $(LDFLAGS) -o $@ $(OBJS)
+
+all: $(BINARY)
+
+clean:
+ rm -f $(BINARY) $(OBJS)
+
+%.o: %.cpp
+ $(CXX) $(CXXFLAGS) -c $< -o $@
diff --git a/Editor/Operation.cpp b/Editor/Operation.cpp
new file mode 100644
index 0000000000..0363ee1ef4
--- /dev/null
+++ b/Editor/Operation.cpp
@@ -0,0 +1,9 @@
+#include "Operation.h"
+
+Operation::Operation()
+{
+}
+
+Operation::~Operation()
+{
+}
diff --git a/Editor/Operation.h b/Editor/Operation.h
new file mode 100644
index 0000000000..03fdf0aac5
--- /dev/null
+++ b/Editor/Operation.h
@@ -0,0 +1,14 @@
+#pragma once
+
+class Editor;
+
+class Operation {
+public:
+ virtual ~Operation();
+
+ virtual bool apply(Editor&) = 0;
+ virtual bool unapply(Editor&) = 0;
+
+protected:
+ Operation();
+};
diff --git a/Editor/Position.h b/Editor/Position.h
new file mode 100644
index 0000000000..6037e5e822
--- /dev/null
+++ b/Editor/Position.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <unistd.h>
+
+class Position {
+public:
+ Position() { }
+ Position(size_t line, size_t column) : m_line(line), m_column(column) { }
+
+ size_t line() const { return m_line; }
+ size_t column() const { return m_column; }
+
+ void set_line(size_t l) { m_line = l; }
+ void set_column(size_t c) { m_column = c; }
+
+ void move_to(size_t l, size_t c) { m_line = l; m_column = c; }
+
+ void move_by(ssize_t l, ssize_t c) { m_line += l; m_column += c; }
+
+ bool is_valid() const { return m_line != InvalidValue && m_column != InvalidValue; }
+
+private:
+ static const size_t InvalidValue = 0xFFFFFFFF;
+
+ size_t m_line { InvalidValue };
+ size_t m_column { InvalidValue };
+};
diff --git a/Editor/UndoStack.cpp b/Editor/UndoStack.cpp
new file mode 100644
index 0000000000..45867d2c79
--- /dev/null
+++ b/Editor/UndoStack.cpp
@@ -0,0 +1,14 @@
+#include "cuki.h"
+#include "UndoStack.h"
+
+void UndoStack::push(OwnPtr<Operation>&& op)
+{
+ m_stack.push(std::move(op));
+}
+
+OwnPtr<Operation> UndoStack::pop()
+{
+ OwnPtr<Operation> op = std::move(m_stack.top());
+ m_stack.pop();
+ return op;
+}
diff --git a/Editor/UndoStack.h b/Editor/UndoStack.h
new file mode 100644
index 0000000000..5938dca216
--- /dev/null
+++ b/Editor/UndoStack.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <stack>
+#include "Operation.h"
+#include "OwnPtr.h"
+
+class UndoStack {
+public:
+ UndoStack() { }
+
+ void push(OwnPtr<Operation>&&);
+ OwnPtr<Operation> pop();
+
+ bool is_empty() const { return m_stack.empty(); }
+
+private:
+ std::stack<OwnPtr<Operation>> m_stack;
+};
diff --git a/Editor/cuki.h b/Editor/cuki.h
new file mode 100644
index 0000000000..80b218bfa0
--- /dev/null
+++ b/Editor/cuki.h
@@ -0,0 +1,6 @@
+// cuki text editor
+
+#include "Assertions.h"
+#include "Noncopyable.h"
+
+// eof
diff --git a/Editor/main.cpp b/Editor/main.cpp
new file mode 100644
index 0000000000..1e9f5a8d41
--- /dev/null
+++ b/Editor/main.cpp
@@ -0,0 +1,19 @@
+#include "Document.h"
+#include "Editor.h"
+#include <stdio.h>
+
+int main(int c, char** v)
+{
+ std::string file_to_open = "cuki.h";
+ if (c > 1) {
+ file_to_open = v[1];
+ }
+ auto document = Document::create_from_file(file_to_open);
+ if (!document) {
+ fprintf(stderr, "Failed to open file.\n");
+ return 1;
+ }
+ Editor editor;
+ editor.set_document(std::move(document));
+ return editor.exec();
+}
diff --git a/Kernel/ELFImage.h b/Kernel/ELFImage.h
index 24bf890a3b..a4decc0ab3 100644
--- a/Kernel/ELFImage.h
+++ b/Kernel/ELFImage.h
@@ -2,7 +2,7 @@
#include <AK/OwnPtr.h>
#include <AK/HashMap.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include "elf.h"
#include "types.h"
diff --git a/Kernel/FIFO.cpp b/Kernel/FIFO.cpp
index 14589d4c10..9b65dc132b 100644
--- a/Kernel/FIFO.cpp
+++ b/Kernel/FIFO.cpp
@@ -1,5 +1,5 @@
#include "FIFO.h"
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
//#define FIFO_DEBUG
diff --git a/Kernel/MemoryManager.h b/Kernel/MemoryManager.h
index 03bb3bbcfe..166486ca97 100644
--- a/Kernel/MemoryManager.h
+++ b/Kernel/MemoryManager.h
@@ -8,7 +8,7 @@
#include <AK/RetainPtr.h>
#include <AK/Vector.h>
#include <AK/HashTable.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <VirtualFileSystem/VirtualFileSystem.h>
class Process;
diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp
index 9ffa7ded49..1a92f1ea7c 100644
--- a/Kernel/Process.cpp
+++ b/Kernel/Process.cpp
@@ -12,7 +12,7 @@
#include "i8253.h"
#include "RTC.h"
#include "ProcFileSystem.h"
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#include <LibC/signal_numbers.h>
#include "Syscall.h"
#include "Scheduler.h"
diff --git a/Kernel/Process.h b/Kernel/Process.h
index 26d612d00e..3e7038b6ab 100644
--- a/Kernel/Process.h
+++ b/Kernel/Process.h
@@ -8,7 +8,7 @@
#include <VirtualFileSystem/VirtualFileSystem.h>
#include <VirtualFileSystem/UnixTypes.h>
#include <AK/InlineLinkedList.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <AK/Vector.h>
class FileDescriptor;
diff --git a/Kernel/VirtualConsole.cpp b/Kernel/VirtualConsole.cpp
index 3e5e85bea7..379f55421d 100644
--- a/Kernel/VirtualConsole.cpp
+++ b/Kernel/VirtualConsole.cpp
@@ -4,7 +4,7 @@
#include "IO.h"
#include "StdLib.h"
#include "Keyboard.h"
-#include <AK/String.h>
+#include <AK/AKString.h>
static byte* s_vga_buffer;
static VirtualConsole* s_consoles[6];
diff --git a/Kernel/system.h b/Kernel/system.h
index 3d2f26b46b..262d1d6d59 100644
--- a/Kernel/system.h
+++ b/Kernel/system.h
@@ -2,7 +2,7 @@
#include "types.h"
#include <AK/Vector.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
struct KSym {
dword address;
diff --git a/LibC/grp.cpp b/LibC/grp.cpp
index e48772627f..c4f2af5f99 100644
--- a/LibC/grp.cpp
+++ b/LibC/grp.cpp
@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
extern "C" {
diff --git a/LibC/pwd.cpp b/LibC/pwd.cpp
index 681bd93c2d..38f3020b26 100644
--- a/LibC/pwd.cpp
+++ b/LibC/pwd.cpp
@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
extern "C" {
diff --git a/LibC/termcap.cpp b/LibC/termcap.cpp
index 1fff6aea38..8bffe27d3f 100644
--- a/LibC/termcap.cpp
+++ b/LibC/termcap.cpp
@@ -3,7 +3,7 @@
#include <string.h>
#include <stdio.h>
#include <AK/HashMap.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
//#define TERMCAP_DEBUG
diff --git a/Userland/kill.cpp b/Userland/kill.cpp
index 7da6429270..5708c7a8d0 100644
--- a/Userland/kill.cpp
+++ b/Userland/kill.cpp
@@ -2,7 +2,7 @@
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
static unsigned parseUInt(const String& str, bool& ok)
{
diff --git a/Userland/ls.cpp b/Userland/ls.cpp
index fa15d8a23b..741b0a3f1b 100644
--- a/Userland/ls.cpp
+++ b/Userland/ls.cpp
@@ -5,7 +5,7 @@
#include <string.h>
#include <getopt.h>
#include <sys/ioctl.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <AK/Vector.h>
static int do_dir(const char* path);
diff --git a/Userland/sleep.cpp b/Userland/sleep.cpp
index 79cffbfc1b..ba50980723 100644
--- a/Userland/sleep.cpp
+++ b/Userland/sleep.cpp
@@ -1,7 +1,7 @@
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
static unsigned parseUInt(const String& str, bool& ok)
{
diff --git a/VirtualFileSystem/Ext2FileSystem.cpp b/VirtualFileSystem/Ext2FileSystem.cpp
index 18d0a4576f..9a07560d64 100644
--- a/VirtualFileSystem/Ext2FileSystem.cpp
+++ b/VirtualFileSystem/Ext2FileSystem.cpp
@@ -2,7 +2,7 @@
#include "ext2_fs.h"
#include "UnixTypes.h"
#include <AK/Bitmap.h>
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#include <AK/kmalloc.h>
#include <AK/ktime.h>
#include <AK/kstdio.h>
diff --git a/VirtualFileSystem/FileBackedDiskDevice.h b/VirtualFileSystem/FileBackedDiskDevice.h
index a42991eda8..9fd630ecad 100644
--- a/VirtualFileSystem/FileBackedDiskDevice.h
+++ b/VirtualFileSystem/FileBackedDiskDevice.h
@@ -2,7 +2,7 @@
#include "DiskDevice.h"
#include <AK/RetainPtr.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <AK/Types.h>
#include <stdio.h>
diff --git a/VirtualFileSystem/FileSystem.h b/VirtualFileSystem/FileSystem.h
index af6a1d1c12..d1f8f67611 100644
--- a/VirtualFileSystem/FileSystem.h
+++ b/VirtualFileSystem/FileSystem.h
@@ -10,7 +10,7 @@
#include <AK/OwnPtr.h>
#include <AK/Retainable.h>
#include <AK/RetainPtr.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <AK/Function.h>
#include <AK/kstdio.h>
diff --git a/VirtualFileSystem/FullDevice.cpp b/VirtualFileSystem/FullDevice.cpp
index 8670652a3d..58f014895f 100644
--- a/VirtualFileSystem/FullDevice.cpp
+++ b/VirtualFileSystem/FullDevice.cpp
@@ -1,7 +1,7 @@
#include "FullDevice.h"
#include "Limits.h"
#include <LibC/errno_numbers.h>
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#include <AK/kstdio.h>
FullDevice::FullDevice()
diff --git a/VirtualFileSystem/NullDevice.cpp b/VirtualFileSystem/NullDevice.cpp
index 61df2b0cde..1ad8c5f784 100644
--- a/VirtualFileSystem/NullDevice.cpp
+++ b/VirtualFileSystem/NullDevice.cpp
@@ -1,6 +1,6 @@
#include "NullDevice.h"
#include "Limits.h"
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#include <AK/kstdio.h>
NullDevice::NullDevice()
diff --git a/VirtualFileSystem/RandomDevice.cpp b/VirtualFileSystem/RandomDevice.cpp
index bcc81a4793..0bd2559085 100644
--- a/VirtualFileSystem/RandomDevice.cpp
+++ b/VirtualFileSystem/RandomDevice.cpp
@@ -1,6 +1,6 @@
#include "RandomDevice.h"
#include "Limits.h"
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
RandomDevice::RandomDevice()
: CharacterDevice(1, 8)
diff --git a/VirtualFileSystem/SyntheticFileSystem.cpp b/VirtualFileSystem/SyntheticFileSystem.cpp
index 9de46db5a8..526d3489f4 100644
--- a/VirtualFileSystem/SyntheticFileSystem.cpp
+++ b/VirtualFileSystem/SyntheticFileSystem.cpp
@@ -1,7 +1,7 @@
#include "SyntheticFileSystem.h"
#include "FileDescriptor.h"
#include <LibC/errno_numbers.h>
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#ifndef SERENITY
typedef int InterruptDisabler;
diff --git a/VirtualFileSystem/VirtualFileSystem.h b/VirtualFileSystem/VirtualFileSystem.h
index 3c24f94314..36695b0395 100644
--- a/VirtualFileSystem/VirtualFileSystem.h
+++ b/VirtualFileSystem/VirtualFileSystem.h
@@ -3,7 +3,7 @@
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/RetainPtr.h>
-#include <AK/String.h>
+#include <AK/AKString.h>
#include <AK/Vector.h>
#include <AK/Function.h>
#include "InodeIdentifier.h"
diff --git a/VirtualFileSystem/ZeroDevice.cpp b/VirtualFileSystem/ZeroDevice.cpp
index 1057e8c947..529f87550b 100644
--- a/VirtualFileSystem/ZeroDevice.cpp
+++ b/VirtualFileSystem/ZeroDevice.cpp
@@ -1,6 +1,6 @@
#include "ZeroDevice.h"
#include "Limits.h"
-#include <AK/StdLib.h>
+#include <AK/StdLibExtras.h>
#include <AK/kstdio.h>
ZeroDevice::ZeroDevice()