Initial commit
This commit is contained in:
commit
535285d9e6
11
GOALS
Normal file
11
GOALS
Normal file
|
@ -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
|
3
IDEAS
Normal file
3
IDEAS
Normal file
|
@ -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
|
17
src/Makefile
Normal file
17
src/Makefile
Normal file
|
@ -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
|
218
src/buffer.cc
Normal file
218
src/buffer.cc
Normal file
|
@ -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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
src/buffer.hh
Normal file
109
src/buffer.hh
Normal file
|
@ -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
|
15
src/display_buffer.cc
Normal file
15
src/display_buffer.cc
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include "display_buffer.hh"
|
||||||
|
|
||||||
|
namespace Kakoune
|
||||||
|
{
|
||||||
|
|
||||||
|
DisplayBuffer::DisplayBuffer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LineAndColumn DisplayBuffer::dimensions() const
|
||||||
|
{
|
||||||
|
return LineAndColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
55
src/display_buffer.hh
Normal file
55
src/display_buffer.hh
Normal file
|
@ -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
|
57
src/file.cc
Normal file
57
src/file.cc
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/file.hh
Normal file
28
src/file.hh
Normal file
|
@ -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
|
323
src/main.cc
Normal file
323
src/main.cc
Normal file
|
@ -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;
|
||||||
|
}
|
33
src/regex_selector.cc
Normal file
33
src/regex_selector.cc
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
src/regex_selector.hh
Normal file
25
src/regex_selector.hh
Normal file
|
@ -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
|
139
src/window.cc
Normal file
139
src/window.cc
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
62
src/window.hh
Normal file
62
src/window.hh
Normal file
|
@ -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
|
Loading…
Reference in New Issue
Block a user