From 535285d9e6008d0c635b85eb6dc9202a3aae11db Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 2 Sep 2011 16:51:20 +0000 Subject: [PATCH] Initial commit --- GOALS | 11 ++ IDEAS | 3 + src/Makefile | 17 +++ src/buffer.cc | 218 ++++++++++++++++++++++++++++ src/buffer.hh | 109 ++++++++++++++ src/display_buffer.cc | 15 ++ src/display_buffer.hh | 55 +++++++ src/file.cc | 57 ++++++++ src/file.hh | 28 ++++ src/main.cc | 323 ++++++++++++++++++++++++++++++++++++++++++ src/regex_selector.cc | 33 +++++ src/regex_selector.hh | 25 ++++ src/window.cc | 139 ++++++++++++++++++ src/window.hh | 62 ++++++++ 14 files changed, 1095 insertions(+) create mode 100644 GOALS create mode 100644 IDEAS create mode 100644 src/Makefile create mode 100644 src/buffer.cc create mode 100644 src/buffer.hh create mode 100644 src/display_buffer.cc create mode 100644 src/display_buffer.hh create mode 100644 src/file.cc create mode 100644 src/file.hh create mode 100644 src/main.cc create mode 100644 src/regex_selector.cc create mode 100644 src/regex_selector.hh create mode 100644 src/window.cc create mode 100644 src/window.hh diff --git a/GOALS b/GOALS new file mode 100644 index 00000000..18e3e49e --- /dev/null +++ b/GOALS @@ -0,0 +1,11 @@ +* Goals (by priority) : + - edition commands should be as fast, or faster (as in less keystroke) than vi + - consistency + - ability tu run background tasks, for exemple :vimgrep equivalent should run while displaying results + - syntax highlighting + - completion framework + - inotify support + +* Non goals : + - No window management, this should be handled by a window manager or screen/tmux, + but the editor should accept multiple heads diff --git a/IDEAS b/IDEAS new file mode 100644 index 00000000..121c06df --- /dev/null +++ b/IDEAS @@ -0,0 +1,3 @@ +* Syntax Highlighting and Folding should be implemented through a 'display filter' concept + A window should have a stack of display filters, that can change the representation of the buffer (adding colors, suppressing lines...) + -> Filters should be used to map window coordinates to buffer coordinates diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..becb8cad --- /dev/null +++ b/src/Makefile @@ -0,0 +1,17 @@ +sources := $(wildcard *.cc) +objects := $(sources:.cc=.o) +deps := $(addprefix ., $(sources:.cc=.d)) + +CXXFLAGS += -std=c++0x -g +LDFLAGS += -lncurses -lboost_regex + +kak : $(objects) + $(CXX) $(LDFLAGS) $(CXXFLAGS) $(objects) -o $@ + +include $(deps) + +.%.d: %.cc + $(CXX) $(CXXFLAGS) -MM $< -o $@ + +clean : + rm -f *.o .*.d kak tags diff --git a/src/buffer.cc b/src/buffer.cc new file mode 100644 index 00000000..1e20b2d6 --- /dev/null +++ b/src/buffer.cc @@ -0,0 +1,218 @@ +#include "buffer.hh" +#include + +namespace Kakoune +{ + +template +T clamp(T min, T max, T val) +{ + if (val < min) + return min; + if (val > max) + return max; + return val; +} + +BufferIterator::BufferIterator(const Buffer& buffer, BufferPos position) : m_buffer(&buffer), + m_position(std::max(0, std::min(position, (BufferPos)buffer.length()))) +{ +} + +BufferIterator& BufferIterator::operator=(const BufferIterator& iterator) +{ + m_buffer == iterator.m_buffer; + m_position = iterator.m_position; +} + +bool BufferIterator::operator==(const BufferIterator& iterator) const +{ + assert(m_buffer == iterator.m_buffer); + return (m_position == iterator.m_position); +} + +bool BufferIterator::operator!=(const BufferIterator& iterator) const +{ + assert(m_buffer == iterator.m_buffer); + return (m_position != iterator.m_position); +} + +bool BufferIterator::operator<(const BufferIterator& iterator) const +{ + assert(m_buffer == iterator.m_buffer); + return (m_position < iterator.m_position); +} + +bool BufferIterator::operator<=(const BufferIterator& iterator) const +{ + assert(m_buffer == iterator.m_buffer); + return (m_position <= iterator.m_position); +} + +BufferChar BufferIterator::operator*() const +{ + assert(m_buffer); + return m_buffer->at(m_position); +} + +BufferSize BufferIterator::operator-(const BufferIterator& iterator) const +{ + assert(m_buffer == iterator.m_buffer); + return static_cast(m_position) - + static_cast(iterator.m_position); +} + +BufferIterator BufferIterator::operator+(BufferSize size) const +{ + assert(m_buffer); + return BufferIterator(*m_buffer, m_position + size); +} + +BufferIterator BufferIterator::operator-(BufferSize size) const +{ + assert(m_buffer); + return BufferIterator(*m_buffer, m_position - size); +} + +BufferIterator& BufferIterator::operator+=(BufferSize size) +{ + assert(m_buffer); + m_position = std::max(0, std::min((BufferSize)m_position + size, + m_buffer->length())); + return *this; +} + +BufferIterator& BufferIterator::operator-=(BufferSize size) +{ + assert(m_buffer); + m_position = std::max(0, std::min((BufferSize)m_position - size, + m_buffer->length())); + return *this; +} + +BufferIterator& BufferIterator::operator++() +{ + return (*this += 1); +} + +BufferIterator& BufferIterator::operator--() +{ + return (*this -= 1); +} + +bool BufferIterator::is_begin() const +{ + assert(m_buffer); + return m_position == 0; +} + +bool BufferIterator::is_end() const +{ + assert(m_buffer); + return m_position == m_buffer->length(); +} + +Buffer::Buffer(const std::string& name) + : m_name(name) +{ +} + +void Buffer::erase(const BufferIterator& begin, const BufferIterator& end) +{ + m_content.erase(begin.m_position, end - begin); + compute_lines(); +} + +void Buffer::insert(const BufferIterator& position, const BufferString& string) +{ + m_content.insert(position.m_position, string); + compute_lines(); +} + +BufferIterator Buffer::iterator_at(const LineAndColumn& line_and_column) const +{ + if (m_lines.empty()) + return begin(); + + BufferPos line = Kakoune::clamp(0, m_lines.size() - 1, line_and_column.line); + BufferPos column = Kakoune::clamp(0, line_length(line), line_and_column.column); + return BufferIterator(*this, m_lines[line] + column); +} + +LineAndColumn Buffer::line_and_column_at(const BufferIterator& iterator) const +{ + LineAndColumn result; + if (not m_lines.empty()) + { + result.line = line_at(iterator); + result.column = iterator.m_position - m_lines[result.line]; + } + return result; +} + +BufferPos Buffer::line_at(const BufferIterator& iterator) const +{ + for (unsigned i = 0; i < m_lines.size(); ++i) + { + if (m_lines[i] > iterator.m_position) + return i - 1; + } + return m_lines.size() - 1; +} + +BufferSize Buffer::line_length(BufferPos line) const +{ + assert(not m_lines.empty()); + BufferPos end = (line >= m_lines.size() - 1) ? + m_content.size() : m_lines[line + 1] - 1; + return end - m_lines[line]; +} + +LineAndColumn Buffer::clamp(const LineAndColumn& line_and_column) const +{ + if (m_lines.empty()) + return LineAndColumn(); + + LineAndColumn result(line_and_column.line, line_and_column.column); + result.line = Kakoune::clamp(0, m_lines.size() - 1, result.line); + result.column = Kakoune::clamp(0, line_length(result.line), result.column); + return result; +} + +void Buffer::compute_lines() +{ + m_lines.clear(); + m_lines.push_back(0); + for (BufferPos i = 0; i < m_content.size(); ++i) + { + if (m_content[i] == '\n') + m_lines.push_back(i + 1); + } +} + +BufferIterator Buffer::begin() const +{ + return BufferIterator(*this, 0); +} + +BufferIterator Buffer::end() const +{ + return BufferIterator(*this, length()); +} + +BufferSize Buffer::length() const +{ + return m_content.size(); +} + +BufferString Buffer::string(const BufferIterator& begin, const BufferIterator& end) const +{ + return m_content.substr(begin.m_position, end - begin); +} + +BufferChar Buffer::at(BufferPos position) const +{ + return m_content[position]; +} + +} diff --git a/src/buffer.hh b/src/buffer.hh new file mode 100644 index 00000000..7564e444 --- /dev/null +++ b/src/buffer.hh @@ -0,0 +1,109 @@ +#ifndef buffer_hh_INCLUDED +#define buffer_hh_INCLUDED + +#include +#include + +namespace Kakoune +{ + +class Buffer; +typedef int BufferPos; +typedef int BufferSize; +typedef char BufferChar; +typedef std::basic_string BufferString; + +struct LineAndColumn +{ + BufferPos line; + BufferPos column; + + LineAndColumn(BufferPos line = 0, BufferPos column = 0) + : line(line), column(column) {} +}; + +class BufferIterator +{ +public: + typedef BufferChar value_type; + typedef BufferSize difference_type; + typedef const value_type* pointer; + typedef const value_type& reference; + typedef std::bidirectional_iterator_tag iterator_category; + + BufferIterator() : m_buffer(NULL), m_position(0) {} + BufferIterator(const Buffer& buffer, BufferPos position); + BufferIterator& operator=(const BufferIterator& iterator); + + bool operator== (const BufferIterator& iterator) const; + bool operator!= (const BufferIterator& iterator) const; + bool operator< (const BufferIterator& iterator) const; + bool operator<= (const BufferIterator& iterator) const; + + BufferChar operator* () const; + BufferSize operator- (const BufferIterator& iterator) const; + + BufferIterator operator+ (BufferSize size) const; + BufferIterator operator- (BufferSize size) const; + + BufferIterator& operator+= (BufferSize size); + BufferIterator& operator-= (BufferSize size); + + BufferIterator& operator++ (); + BufferIterator& operator-- (); + + bool is_begin() const; + bool is_end() const; + +private: + const Buffer* m_buffer; + BufferPos m_position; + friend class Buffer; +}; + +class Buffer +{ +public: + Buffer(const std::string& name); + + void erase(const BufferIterator& begin, + const BufferIterator& end); + + void insert(const BufferIterator& position, + const BufferString& string); + + BufferString string(const BufferIterator& begin, + const BufferIterator& end) const; + + BufferIterator begin() const; + BufferIterator end() const; + + BufferSize length() const; + + BufferIterator iterator_at(const LineAndColumn& line_and_column) const; + LineAndColumn line_and_column_at(const BufferIterator& iterator) const; + + LineAndColumn clamp(const LineAndColumn& line_and_column) const; + + const std::string& name() const { return m_name; } + + const BufferString& content() const { return m_content; } + +private: + BufferChar at(BufferPos position) const; + friend class BufferIterator; + + std::vector m_lines; + + void compute_lines(); + BufferPos line_at(const BufferIterator& iterator) const; + BufferSize line_length(BufferPos line) const; + + BufferString m_content; + + std::string m_name; +}; + +} + +#endif // buffer_hh_INCLUDED diff --git a/src/display_buffer.cc b/src/display_buffer.cc new file mode 100644 index 00000000..8a033318 --- /dev/null +++ b/src/display_buffer.cc @@ -0,0 +1,15 @@ +#include "display_buffer.hh" + +namespace Kakoune +{ + +DisplayBuffer::DisplayBuffer() +{ +} + +LineAndColumn DisplayBuffer::dimensions() const +{ + return LineAndColumn(); +} + +} diff --git a/src/display_buffer.hh b/src/display_buffer.hh new file mode 100644 index 00000000..bde04bed --- /dev/null +++ b/src/display_buffer.hh @@ -0,0 +1,55 @@ +#ifndef display_buffer_hh_INCLUDED +#define display_buffer_hh_INCLUDED + +#include +#include + +#include "buffer.hh" + +namespace Kakoune +{ + +typedef int Color; +typedef int Attribute; + +enum Attributes +{ + UNDERLINE = 1 +}; + +struct DisplayAtom +{ + std::string content; + Color fg_color; + Color bg_color; + Attribute attribute; + + DisplayAtom() : fg_color(0), bg_color(0), attribute(0) {} +}; + +class DisplayBuffer +{ +public: + typedef std::vector AtomList; + typedef AtomList::iterator iterator; + typedef AtomList::const_iterator const_iterator; + + DisplayBuffer(); + + LineAndColumn dimensions() const; + + void clear() { m_atoms.clear(); } + void append(const DisplayAtom& atom) { m_atoms.push_back(atom); } + + iterator begin() { return m_atoms.begin(); } + iterator end() { return m_atoms.end(); } + + const_iterator begin() const { return m_atoms.begin(); } + const_iterator end() const { return m_atoms.end(); } +private: + AtomList m_atoms; +}; + +} + +#endif // display_buffer_hh_INCLUDED diff --git a/src/file.cc b/src/file.cc new file mode 100644 index 00000000..17150248 --- /dev/null +++ b/src/file.cc @@ -0,0 +1,57 @@ +#include "file.hh" +#include "buffer.hh" + +#include +#include +#include +#include +#include + +namespace Kakoune +{ + +Buffer* create_buffer_from_file(const std::string& filename) +{ + int fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) + throw open_file_error(strerror(errno)); + + std::string content; + char buf[256]; + while (true) + { + ssize_t size = read(fd, buf, 256); + if (size == -1 or size == 0) + break; + + content += std::string(buf, size); + } + close(fd); + Buffer* buffer = new Buffer(filename); + buffer->insert(buffer->begin(), content); + return buffer; +} + +void write_buffer_to_file(const Buffer& buffer, const std::string& filename) +{ + int fd = open(filename.c_str(), O_CREAT | O_WRONLY, 0644); + if (fd == -1) + throw open_file_error(strerror(errno)); + + const BufferString& content = buffer.content(); + ssize_t count = content.length() * sizeof(BufferChar); + const char* ptr = content.c_str(); + + while (count) + { + ssize_t written = write(fd, ptr, count); + ptr += written; + count -= written; + + if (written == -1) + throw write_file_error(strerror(errno)); + } + close(fd); +} + +} diff --git a/src/file.hh b/src/file.hh new file mode 100644 index 00000000..0a4c9d06 --- /dev/null +++ b/src/file.hh @@ -0,0 +1,28 @@ +#ifndef file_hh_INCLUDED +#define file_hh_INCLUDED + +#include +#include + +namespace Kakoune +{ + +struct open_file_error : public std::runtime_error +{ + open_file_error(const std::string& what) + : std::runtime_error(what) {} +}; + +struct write_file_error : public std::runtime_error +{ + write_file_error(const std::string& what) + : std::runtime_error(what) {} +}; + +class Buffer; +Buffer* create_buffer_from_file(const std::string& filename); +void write_buffer_to_file(const Buffer& buffer, const std::string& filename); + +} + +#endif // file_hh_INCLUDED diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..54841c06 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,323 @@ +#include +#include "window.hh" +#include "buffer.hh" +#include "file.hh" +#include "regex_selector.hh" + +#include +#include + +using namespace Kakoune; + +void draw_window(Window& window) +{ + window.update_display_buffer(); + + int max_x,max_y; + getmaxyx(stdscr, max_y, max_x); + max_y -= 1; + + LineAndColumn position; + for (const DisplayAtom& atom : window.display_buffer()) + { + const std::string& content = atom.content; + + if (atom.attribute & UNDERLINE) + attron(A_UNDERLINE); + else + attroff(A_UNDERLINE); + + size_t pos = 0; + size_t end; + while (true) + { + move(position.line, position.column); + clrtoeol(); + end = content.find_first_of('\n', pos); + std::string line = content.substr(pos, end - pos); + addstr(line.c_str()); + + if (end != std::string::npos) + { + position.line = position.line + 1; + position.column = 0; + pos = end + 1; + + if (position.line >= max_y) + break; + } + else + { + position.column += line.length(); + break; + } + } + if (position.line >= max_y) + break; + } + while (++position.line < max_y) + { + move(position.line, 0); + clrtoeol(); + addch('~'); + } + + const LineAndColumn& cursor_position = window.cursor_position(); + move(cursor_position.line, cursor_position.column); +} + +void init_ncurses() +{ + initscr(); + cbreak(); + noecho(); + nonl(); + intrflush(stdscr, false); + keypad(stdscr, true); + curs_set(2); +} + +void deinit_ncurses() +{ + endwin(); +} + +void do_insert(Window& window) +{ + std::string inserted; + LineAndColumn pos = window.cursor_position(); + while(true) + { + char c = getch(); + if (c == 27) + break; + + window.insert(std::string() + c); + draw_window(window); + } +} + +std::shared_ptr current_window; + +void edit(const std::string& filename) +{ + try + { + Buffer* buffer = create_buffer_from_file(filename); + if (buffer) + current_window = std::make_shared(std::shared_ptr(buffer)); + } + catch (open_file_error& what) + { + assert(false); + } +} + +void write_buffer(const std::string& filename) +{ + try + { + write_buffer_to_file(*current_window->buffer(), filename); + } + catch(open_file_error& what) + { + assert(false); + } + catch(write_file_error& what) + { + assert(false); + } +} + +bool quit_requested = false; + +void quit(const std::string&) +{ + quit_requested = true; +} + +std::unordered_map> cmdmap = +{ + { "e", edit }, + { "edit", edit }, + { "q", quit }, + { "quit", quit }, + { "w", write_buffer }, + { "write", write_buffer }, +}; + +struct prompt_aborted {}; + +std::string prompt(const std::string& text) +{ + int max_x, max_y; + getmaxyx(stdscr, max_y, max_x); + move(max_y-1, 0); + addstr(text.c_str()); + clrtoeol(); + + std::string result; + while(true) + { + char c = getch(); + switch (c) + { + case '\r': + return result; + case 7: + move(max_y - 1, text.length() + result.length() - 1); + addch(' '); + result.resize(result.length() - 1); + move(max_y - 1, text.length() + result.length()); + refresh; + break; + case 27: + throw prompt_aborted(); + default: + result += c; + addch(c); + refresh(); + } + } + return result; +} + +void print_status(const std::string& status) +{ + int x,y; + getmaxyx(stdscr, y, x); + move(y-1, 0); + clrtoeol(); + addstr(status.c_str()); +} + +void do_command() +{ + try + { + std::string cmd = prompt(":"); + + size_t cmd_end = cmd.find_first_of(' '); + std::string cmd_name = cmd.substr(0, cmd_end); + size_t param_start = cmd.find_first_not_of(' ', cmd_end); + std::string param; + if (param_start != std::string::npos) + param = cmd.substr(param_start, cmd.length() - param_start); + + if (cmdmap.find(cmd_name) != cmdmap.end()) + cmdmap[cmd_name](param); + else + print_status(cmd_name + ": no such command"); + } + catch (prompt_aborted&) {} +} + +bool is_blank(char c) +{ + return c == ' ' or c == '\t' or c == '\n'; +} + +Selection select_to_next_word(const BufferIterator& cursor) +{ + BufferIterator end = cursor; + while (not end.is_end() and not is_blank(*end)) + ++end; + + while (not end.is_end() and is_blank(*end)) + ++end; + + return Selection(cursor, end); +} + +Selection select_to_next_word_end(const BufferIterator& cursor) +{ + BufferIterator end = cursor; + while (not end.is_end() and is_blank(*end)) + ++end; + + while (not end.is_end() and not is_blank(*end)) + ++end; + + return Selection(cursor, end); +} + +Selection select_line(const BufferIterator& cursor) +{ + BufferIterator begin = cursor; + while (not begin.is_begin() and *(begin -1) != '\n') + --begin; + + BufferIterator end = cursor; + while (not end.is_end() and *end != '\n') + ++end; + return Selection(begin, end + 1); +} + +void do_search(Window& window) +{ + try + { + std::string ex = prompt("/"); + window.select(false, RegexSelector(ex)); + } + catch (boost::regex_error&) {} + catch (prompt_aborted&) {} +} + +std::unordered_map> keymap = +{ + { 'h', [](Window& window, int count) { if (count == 0) count = 1; window.move_cursor(LineAndColumn(0, -count)); window.empty_selections(); } }, + { 'j', [](Window& window, int count) { if (count == 0) count = 1; window.move_cursor(LineAndColumn(count, 0)); window.empty_selections(); } }, + { 'k', [](Window& window, int count) { if (count == 0) count = 1; window.move_cursor(LineAndColumn(-count, 0)); window.empty_selections(); } }, + { 'l', [](Window& window, int count) { if (count == 0) count = 1; window.move_cursor(LineAndColumn(0, count)); window.empty_selections(); } }, + { 'd', [](Window& window, int count) { window.erase(); window.empty_selections(); } }, + { 'c', [](Window& window, int count) { window.erase(); do_insert(window); } }, + { 'i', [](Window& window, int count) { do_insert(window); } }, + { ':', [](Window& window, int count) { do_command(); } }, + { ' ', [](Window& window, int count) { window.empty_selections(); } }, + { 'w', [](Window& window, int count) { do { window.select(false, select_to_next_word); } while(--count > 0); } }, + { 'W', [](Window& window, int count) { do { window.select(true, select_to_next_word); } while(--count > 0); } }, + { 'e', [](Window& window, int count) { do { window.select(false, select_to_next_word_end); } while(--count > 0); } }, + { 'E', [](Window& window, int count) { do { window.select(true, select_to_next_word_end); } while(--count > 0); } }, + { '.', [](Window& window, int count) { do { window.select(false, select_line); } while(--count > 0); } }, + { '/', [](Window& window, int count) { do_search(window); } }, +}; + +int main() +{ + init_ncurses(); + + try + { + auto buffer = std::make_shared(""); + current_window = std::make_shared(buffer); + + draw_window(*current_window); + int count = 0; + while(not quit_requested) + { + char c = getch(); + + if (isdigit(c)) + count = count * 10 + c - '0'; + else + { + if (keymap.find(c) != keymap.end()) + { + keymap[c](*current_window, count); + draw_window(*current_window); + } + count = 0; + } + } + deinit_ncurses(); + } + catch (std::runtime_error& error) + { + deinit_ncurses(); + puts("unhandled exception : "); + puts(error.what()); + return -1; + } + return 0; +} diff --git a/src/regex_selector.cc b/src/regex_selector.cc new file mode 100644 index 00000000..d8b409a3 --- /dev/null +++ b/src/regex_selector.cc @@ -0,0 +1,33 @@ +#include "regex_selector.hh" + +void print_status(const std::string&); + +namespace Kakoune +{ + +RegexSelector::RegexSelector(const std::string& exp) + : m_regex(exp) {} + +Selection RegexSelector::operator()(const BufferIterator& cursor) const +{ + BufferIterator line_end = cursor + 1; + + try + { + while (not line_end.is_end() and *line_end != '\n') + ++line_end; + + boost::match_results matches; + + if (boost::regex_search(cursor, line_end, matches, m_regex)) + return Selection(matches.begin()->first, matches.begin()->second); + } + catch (boost::regex_error& err) + { + print_status("regex error"); + } + + return Selection(cursor, cursor); +} + +} diff --git a/src/regex_selector.hh b/src/regex_selector.hh new file mode 100644 index 00000000..7489bb72 --- /dev/null +++ b/src/regex_selector.hh @@ -0,0 +1,25 @@ +#ifndef regex_selector_hh_INCLUDED +#define regex_selector_hh_INCLUDED + +#include "buffer.hh" +#include "window.hh" + +#include + +namespace Kakoune +{ + +class RegexSelector +{ +public: + RegexSelector(const std::string& exp); + + Selection operator()(const BufferIterator& cursor) const; + +private: + boost::regex m_regex; +}; + +} + +#endif // regex_selector_hh_INCLUDED diff --git a/src/window.cc b/src/window.cc new file mode 100644 index 00000000..4a889c00 --- /dev/null +++ b/src/window.cc @@ -0,0 +1,139 @@ +#include "window.hh" + +#include + +namespace Kakoune +{ + +Window::Window(const std::shared_ptr buffer) + : m_buffer(buffer), + m_position(0, 0), + m_cursor(0, 0) +{ +} + +void Window::erase() +{ + if (m_selections.empty()) + { + BufferIterator cursor = m_buffer->iterator_at(m_cursor); + m_buffer->erase(cursor, cursor+1); + } + + for (auto sel = m_selections.begin(); sel != m_selections.end(); ++sel) + { + m_buffer->erase(sel->begin, sel->end); + sel->end = sel->begin; + } +} + +static LineAndColumn measure_string(const Window::String& string) +{ + LineAndColumn result(0, 0); + for (size_t i = 0; i < string.length(); ++i) + { + if (string[i] == '\n') + { + ++result.line; + result.column = 0; + } + else + ++result.column; + } + return result; +} + +void Window::insert(const String& string) +{ + if (m_selections.empty()) + { + m_buffer->insert(m_buffer->iterator_at(m_cursor), string); + move_cursor(measure_string(string)); + } + + for (auto sel = m_selections.begin(); sel != m_selections.end(); ++sel) + { + m_buffer->insert(sel->begin, string); + sel->begin += string.length(); + sel->end += string.length(); + } +} + +void Window::append(const String& string) +{ + if (m_selections.empty()) + { + move_cursor(LineAndColumn(0 , 1)); + insert(string); + } + + for (auto sel = m_selections.begin(); sel != m_selections.end(); ++sel) + { + m_buffer->insert(sel->end, string); + } +} + +void Window::empty_selections() +{ + m_selections.clear(); +} + +void Window::select(bool append, const Selector& selector) +{ + if (not append or m_selections.empty()) + { + empty_selections(); + m_selections.push_back(selector(m_buffer->iterator_at(m_cursor))); + } + else + { + for (auto sel = m_selections.begin(); sel != m_selections.end(); ++sel) + { + sel->end = selector(sel->end).end; + } + } + m_cursor = m_buffer->line_and_column_at(m_selections.back().end); +} + +void Window::move_cursor(const LineAndColumn& offset) +{ + m_cursor = m_buffer->clamp(LineAndColumn(m_cursor.line + offset.line, + m_cursor.column + offset.column)); +} + +void Window::update_display_buffer() +{ + m_display_buffer.clear(); + + SelectionList sorted_selections = m_selections; + std::sort(sorted_selections.begin(), sorted_selections.end(), + [](const Selection& lhs, const Selection& rhs) { return lhs.begin < rhs.begin; }); + + BufferIterator current_position = m_buffer->begin(); + + for (Selection& sel : sorted_selections) + { + if (current_position != sel.begin) + { + DisplayAtom atom; + atom.content = m_buffer->string(current_position, sel.begin); + m_display_buffer.append(atom); + } + if (sel.begin != sel.end) + { + DisplayAtom atom; + atom.content = m_buffer->string(sel.begin, sel.end); + atom.attribute = UNDERLINE; + m_display_buffer.append(atom); + } + current_position = sel.end; + } + if (current_position != m_buffer->end()) + { + DisplayAtom atom; + atom.content = m_buffer->string(current_position, m_buffer->end()); + m_display_buffer.append(atom); + } +} + +} diff --git a/src/window.hh b/src/window.hh new file mode 100644 index 00000000..6df0cd70 --- /dev/null +++ b/src/window.hh @@ -0,0 +1,62 @@ +#ifndef window_hh_INCLUDED +#define window_hh_INCLUDED + +#include +#include +#include "buffer.hh" +#include "display_buffer.hh" + +namespace Kakoune +{ + +struct Selection +{ + Selection(const BufferIterator& begin, const BufferIterator& end) + : begin(begin), end(end) {} + + BufferIterator begin; + BufferIterator end; +}; + +typedef std::vector SelectionList; + +class Window +{ +public: + typedef BufferString String; + typedef std::function Selector; + + Window(const std::shared_ptr buffer); + Window(const Window&) = delete; + + void erase(); + void insert(const String& string); + void append(const String& string); + + const LineAndColumn& position() const { return m_position; } + const LineAndColumn& cursor_position() const { return m_cursor; } + const std::shared_ptr& buffer() const { return m_buffer; } + + void move_cursor(const LineAndColumn& offset); + + const SelectionList& selections() const { return m_selections; } + + void empty_selections(); + void select(bool append, const Selector& selector); + + const DisplayBuffer& display_buffer() const { return m_display_buffer; } + + void update_display_buffer(); +private: + + std::shared_ptr m_buffer; + LineAndColumn m_position; + LineAndColumn m_cursor; + LineAndColumn m_dimensions; + SelectionList m_selections; + DisplayBuffer m_display_buffer; +}; + +} + +#endif // window_hh_INCLUDED