mirror of
https://github.com/mawww/kakoune.git
synced 2024-11-27 02:23:26 +03:00
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