summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMaxime Coste <frrrwww@gmail.com>2011-09-02 16:51:20 +0000
committerMaxime Coste <frrrwww@gmail.com>2011-09-02 16:51:20 +0000
commit535285d9e6008d0c635b85eb6dc9202a3aae11db (patch)
treec5838219a067cbee262a79045f0cb5a71813c1f2 /src
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/Makefile17
-rw-r--r--src/buffer.cc218
-rw-r--r--src/buffer.hh109
-rw-r--r--src/display_buffer.cc15
-rw-r--r--src/display_buffer.hh55
-rw-r--r--src/file.cc57
-rw-r--r--src/file.hh28
-rw-r--r--src/main.cc323
-rw-r--r--src/regex_selector.cc33
-rw-r--r--src/regex_selector.hh25
-rw-r--r--src/window.cc139
-rw-r--r--src/window.hh62
12 files changed, 1081 insertions, 0 deletions
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 <cassert>
+
+namespace Kakoune
+{
+
+template<typename T>
+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<BufferSize>(m_position) -
+ static_cast<BufferSize>(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<int>(0, m_lines.size() - 1, line_and_column.line);
+ BufferPos column = Kakoune::clamp<int>(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<int>(0, m_lines.size() - 1, result.line);
+ result.column = Kakoune::clamp<int>(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 <string>
+#include <vector>
+
+namespace Kakoune
+{
+
+class Buffer;
+typedef int BufferPos;
+typedef int BufferSize;
+typedef char BufferChar;
+typedef std::basic_string<BufferChar> 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<BufferPos> 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 <string>
+#include <vector>
+
+#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<DisplayAtom> 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <cstring>
+#include <cassert>
+
+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 <string>
+#include <stdexcept>
+
+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 <ncurses.h>
+#include "window.hh"
+#include "buffer.hh"
+#include "file.hh"
+#include "regex_selector.hh"
+
+#include <unordered_map>
+#include <cassert>
+
+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<Window> current_window;
+
+void edit(const std::string& filename)
+{
+ try
+ {
+ Buffer* buffer = create_buffer_from_file(filename);
+ if (buffer)
+ current_window = std::make_shared<Window>(std::shared_ptr<Buffer>(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<std::string, std::function<void (const std::string& param)>> 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<char, std::function<void (Window& window, int count)>> 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<Buffer>("<scratch>");
+ current_window = std::make_shared<Window>(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<BufferIterator> 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 <boost/regex.hpp>
+
+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 <algorithm>
+
+namespace Kakoune
+{
+
+Window::Window(const std::shared_ptr<Buffer> 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 <memory>
+#include <functional>
+#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<Selection> SelectionList;
+
+class Window
+{
+public:
+ typedef BufferString String;
+ typedef std::function<Selection (const BufferIterator&)> Selector;
+
+ Window(const std::shared_ptr<Buffer> 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>& 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<Buffer> m_buffer;
+ LineAndColumn m_position;
+ LineAndColumn m_cursor;
+ LineAndColumn m_dimensions;
+ SelectionList m_selections;
+ DisplayBuffer m_display_buffer;
+};
+
+}
+
+#endif // window_hh_INCLUDED