mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge PR #3246 'Run builtin TUI in a another thread'
This commit is contained in:
commit
bb46cc2c9c
@ -61,19 +61,25 @@ build_nvim() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building libnvim."
|
if [ "$CLANG_SANITIZER" != "TSAN" ]; then
|
||||||
if ! ${MAKE_CMD} libnvim; then
|
echo "Building libnvim."
|
||||||
exit 1
|
if ! ${MAKE_CMD} libnvim; then
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Building nvim-test."
|
echo "Building nvim-test."
|
||||||
if ! ${MAKE_CMD} nvim-test; then
|
if ! ${MAKE_CMD} nvim-test; then
|
||||||
exit 1
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Invoke nvim to trigger *San early.
|
# Invoke nvim to trigger *San early.
|
||||||
bin/nvim --version
|
if ! (bin/nvim --version && bin/nvim -u NONE -e -c ':qall'); then
|
||||||
bin/nvim -u NONE -e -c ':qall'
|
asan_check "${LOG_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
asan_check "${LOG_DIR}"
|
||||||
|
|
||||||
|
|
||||||
cd "${TRAVIS_BUILD_DIR}"
|
cd "${TRAVIS_BUILD_DIR}"
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,12 @@ source "${CI_DIR}/common/test.sh"
|
|||||||
|
|
||||||
build_nvim
|
build_nvim
|
||||||
|
|
||||||
run_unittests
|
if [ "$CLANG_SANITIZER" != "TSAN" ]; then
|
||||||
run_functionaltests
|
# Additional threads are only created when the builtin UI starts, which
|
||||||
|
# doesn't happen in the unit/functional tests
|
||||||
|
run_unittests
|
||||||
|
run_functionaltests
|
||||||
|
fi
|
||||||
run_oldtests
|
run_oldtests
|
||||||
|
|
||||||
install_nvim
|
install_nvim
|
||||||
|
@ -77,10 +77,9 @@ matrix:
|
|||||||
- os: linux
|
- os: linux
|
||||||
compiler: clang-3.6
|
compiler: clang-3.6
|
||||||
env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=MSAN
|
env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=MSAN
|
||||||
# FIXME (tarruda): Uncomment when TSan tests don't hang anymore.
|
- os: linux
|
||||||
#- os: linux
|
compiler: clang-3.6
|
||||||
# compiler: clang-3.6
|
env: CLANG_SANITIZER=TSAN
|
||||||
# env: GCOV=llvm-cov-3.6 CLANG_SANITIZER=TSAN
|
|
||||||
- os: osx
|
- os: osx
|
||||||
compiler: clang
|
compiler: clang
|
||||||
env: GCOV=gcov
|
env: GCOV=gcov
|
||||||
|
92
contrib/gdb/nvim-gdb-pretty-printers.py
Normal file
92
contrib/gdb/nvim-gdb-pretty-printers.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Register a gdb pretty printer for UGrid instances. Usage:
|
||||||
|
#
|
||||||
|
# - start gdb
|
||||||
|
# - run `source contrib/gdb/nvim-gdb-pretty-printers.py`
|
||||||
|
# - when a `UGrid` pointer can be evaluated in the current frame, just print
|
||||||
|
# it's value normally: `p *grid` (assuming `grid` is the variable name
|
||||||
|
# holding the pointer)
|
||||||
|
# - highlighting can be activated by setting the NVIM_GDB_HIGHLIGHT_UGRID
|
||||||
|
# environment variable(only xterm-compatible terminals supported). This
|
||||||
|
# can be done while gdb is running through the python interface:
|
||||||
|
# `python os.environ['NVIM_GDB_HIGHLIGHT_UGRID'] = '1'`
|
||||||
|
import os
|
||||||
|
import gdb
|
||||||
|
import gdb.printing
|
||||||
|
|
||||||
|
|
||||||
|
SGR0 = '\x1b(B\x1b[m'
|
||||||
|
|
||||||
|
|
||||||
|
def get_color_code(bg, color_num):
|
||||||
|
if color_num < 16:
|
||||||
|
prefix = 3
|
||||||
|
if color_num > 7:
|
||||||
|
prefix = 9
|
||||||
|
if bg:
|
||||||
|
prefix += 1
|
||||||
|
color_num %= 8
|
||||||
|
else:
|
||||||
|
prefix = '48;5;' if bg else '38;5;'
|
||||||
|
return '\x1b[{0}{1}m'.format(prefix, color_num)
|
||||||
|
|
||||||
|
|
||||||
|
def highlight(attrs):
|
||||||
|
fg, bg = [int(attrs['foreground']), int(attrs['background'])]
|
||||||
|
rv = [SGR0] # start with sgr0
|
||||||
|
if fg != -1:
|
||||||
|
rv.append(get_color_code(False, fg))
|
||||||
|
if bg != -1:
|
||||||
|
rv.append(get_color_code(True, bg))
|
||||||
|
if bool(attrs['bold']):
|
||||||
|
rv.append('\x1b[1m')
|
||||||
|
if bool(attrs['italic']):
|
||||||
|
rv.append('\x1b[3m')
|
||||||
|
if bool(attrs['undercurl']) or bool(attrs['underline']):
|
||||||
|
rv.append('\x1b[4m')
|
||||||
|
if bool(attrs['reverse']):
|
||||||
|
rv.append('\x1b[7m')
|
||||||
|
return ''.join(rv)
|
||||||
|
|
||||||
|
|
||||||
|
class UGridPrinter(object):
|
||||||
|
def __init__(self, val):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
def to_string(self):
|
||||||
|
do_hl = (os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') and
|
||||||
|
os.getenv('NVIM_GDB_HIGHLIGHT_UGRID') != '0')
|
||||||
|
grid = self.val
|
||||||
|
height = int(grid['height'])
|
||||||
|
width = int(grid['width'])
|
||||||
|
delimiter = '-' * (width + 2)
|
||||||
|
rows = [delimiter]
|
||||||
|
for row in range(height):
|
||||||
|
cols = []
|
||||||
|
if do_hl:
|
||||||
|
cols.append(SGR0)
|
||||||
|
curhl = None
|
||||||
|
for col in range(width):
|
||||||
|
cell = grid['cells'][row][col]
|
||||||
|
if do_hl:
|
||||||
|
hl = highlight(cell['attrs'])
|
||||||
|
if hl != curhl:
|
||||||
|
cols.append(hl)
|
||||||
|
curhl = hl
|
||||||
|
cols.append(cell['data'].string('utf-8'))
|
||||||
|
if do_hl:
|
||||||
|
cols.append(SGR0)
|
||||||
|
rows.append('|' + ''.join(cols) + '|')
|
||||||
|
rows.append(delimiter)
|
||||||
|
return '\n' + '\n'.join(rows)
|
||||||
|
|
||||||
|
def display_hint(self):
|
||||||
|
return 'hint'
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_printers():
|
||||||
|
pp = gdb.printing.RegexpCollectionPrettyPrinter('nvim')
|
||||||
|
pp.add_printer('UGrid', '^ugrid$', UGridPrinter)
|
||||||
|
return pp
|
||||||
|
|
||||||
|
|
||||||
|
gdb.printing.register_pretty_printer(gdb, pretty_printers(), replace=True)
|
@ -2,12 +2,15 @@ include(CheckLibraryExists)
|
|||||||
|
|
||||||
option(USE_GCOV "Enable gcov support" OFF)
|
option(USE_GCOV "Enable gcov support" OFF)
|
||||||
|
|
||||||
|
if(NOT CLANG_TSAN)
|
||||||
|
# GCOV and TSAN results in false data race reports
|
||||||
if(USE_GCOV)
|
if(USE_GCOV)
|
||||||
message(STATUS "Enabling gcov support")
|
message(STATUS "Enabling gcov support")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
|
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
|
||||||
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)
|
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
#define EVENT_HANDLER_MAX_ARGC 4
|
#define EVENT_HANDLER_MAX_ARGC 6
|
||||||
|
|
||||||
typedef void (*argv_callback)(void **argv);
|
typedef void (*argv_callback)(void **argv);
|
||||||
typedef struct message {
|
typedef struct message {
|
||||||
@ -12,6 +12,7 @@ typedef struct message {
|
|||||||
argv_callback handler;
|
argv_callback handler;
|
||||||
void *argv[EVENT_HANDLER_MAX_ARGC];
|
void *argv[EVENT_HANDLER_MAX_ARGC];
|
||||||
} Event;
|
} Event;
|
||||||
|
typedef void(*event_scheduler)(Event event, void *data);
|
||||||
|
|
||||||
#define VA_EVENT_INIT(event, p, h, a) \
|
#define VA_EVENT_INIT(event, p, h, a) \
|
||||||
do { \
|
do { \
|
||||||
|
@ -10,20 +10,19 @@
|
|||||||
# include "event/loop.c.generated.h"
|
# include "event/loop.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct idle_event {
|
|
||||||
uv_idle_t idle;
|
|
||||||
Event event;
|
|
||||||
} IdleEvent;
|
|
||||||
|
|
||||||
|
|
||||||
void loop_init(Loop *loop, void *data)
|
void loop_init(Loop *loop, void *data)
|
||||||
{
|
{
|
||||||
uv_loop_init(&loop->uv);
|
uv_loop_init(&loop->uv);
|
||||||
|
loop->recursive = 0;
|
||||||
loop->uv.data = loop;
|
loop->uv.data = loop;
|
||||||
loop->children = kl_init(WatcherPtr);
|
loop->children = kl_init(WatcherPtr);
|
||||||
loop->children_stop_requests = 0;
|
loop->children_stop_requests = 0;
|
||||||
loop->events = queue_new_parent(loop_on_put, loop);
|
loop->events = queue_new_parent(loop_on_put, loop);
|
||||||
loop->fast_events = queue_new_child(loop->events);
|
loop->fast_events = queue_new_child(loop->events);
|
||||||
|
loop->thread_events = queue_new_parent(NULL, NULL);
|
||||||
|
uv_mutex_init(&loop->mutex);
|
||||||
|
uv_async_init(&loop->uv, &loop->async, async_cb);
|
||||||
uv_signal_init(&loop->uv, &loop->children_watcher);
|
uv_signal_init(&loop->uv, &loop->children_watcher);
|
||||||
uv_timer_init(&loop->uv, &loop->children_kill_timer);
|
uv_timer_init(&loop->uv, &loop->children_kill_timer);
|
||||||
uv_timer_init(&loop->uv, &loop->poll_timer);
|
uv_timer_init(&loop->uv, &loop->poll_timer);
|
||||||
@ -31,9 +30,7 @@ void loop_init(Loop *loop, void *data)
|
|||||||
|
|
||||||
void loop_poll_events(Loop *loop, int ms)
|
void loop_poll_events(Loop *loop, int ms)
|
||||||
{
|
{
|
||||||
static int recursive = 0;
|
if (loop->recursive++) {
|
||||||
|
|
||||||
if (recursive++) {
|
|
||||||
abort(); // Should not re-enter uv_run
|
abort(); // Should not re-enter uv_run
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,10 +52,19 @@ void loop_poll_events(Loop *loop, int ms)
|
|||||||
uv_timer_stop(&loop->poll_timer);
|
uv_timer_stop(&loop->poll_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
recursive--; // Can re-enter uv_run now
|
loop->recursive--; // Can re-enter uv_run now
|
||||||
queue_process_events(loop->fast_events);
|
queue_process_events(loop->fast_events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule an event from another thread
|
||||||
|
void loop_schedule(Loop *loop, Event event)
|
||||||
|
{
|
||||||
|
uv_mutex_lock(&loop->mutex);
|
||||||
|
queue_put_event(loop->thread_events, event);
|
||||||
|
uv_async_send(&loop->async);
|
||||||
|
uv_mutex_unlock(&loop->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
void loop_on_put(Queue *queue, void *data)
|
void loop_on_put(Queue *queue, void *data)
|
||||||
{
|
{
|
||||||
Loop *loop = data;
|
Loop *loop = data;
|
||||||
@ -72,14 +78,32 @@ void loop_on_put(Queue *queue, void *data)
|
|||||||
|
|
||||||
void loop_close(Loop *loop)
|
void loop_close(Loop *loop)
|
||||||
{
|
{
|
||||||
|
uv_mutex_destroy(&loop->mutex);
|
||||||
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
|
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
|
||||||
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
|
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
|
||||||
uv_close((uv_handle_t *)&loop->poll_timer, NULL);
|
uv_close((uv_handle_t *)&loop->poll_timer, NULL);
|
||||||
|
uv_close((uv_handle_t *)&loop->async, NULL);
|
||||||
do {
|
do {
|
||||||
uv_run(&loop->uv, UV_RUN_DEFAULT);
|
uv_run(&loop->uv, UV_RUN_DEFAULT);
|
||||||
} while (uv_loop_close(&loop->uv));
|
} while (uv_loop_close(&loop->uv));
|
||||||
|
queue_free(loop->events);
|
||||||
|
queue_free(loop->fast_events);
|
||||||
|
queue_free(loop->thread_events);
|
||||||
|
kl_destroy(WatcherPtr, loop->children);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void async_cb(uv_async_t *handle)
|
||||||
|
{
|
||||||
|
Loop *l = handle->loop->data;
|
||||||
|
uv_mutex_lock(&l->mutex);
|
||||||
|
while (!queue_empty(l->thread_events)) {
|
||||||
|
Event ev = queue_get(l->thread_events);
|
||||||
|
queue_put_event(l->fast_events, ev);
|
||||||
|
}
|
||||||
|
uv_mutex_unlock(&l->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void timer_cb(uv_timer_t *handle)
|
static void timer_cb(uv_timer_t *handle)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,14 @@ KLIST_INIT(WatcherPtr, WatcherPtr, _noop)
|
|||||||
|
|
||||||
typedef struct loop {
|
typedef struct loop {
|
||||||
uv_loop_t uv;
|
uv_loop_t uv;
|
||||||
Queue *events, *fast_events;
|
Queue *events, *fast_events, *thread_events;
|
||||||
klist_t(WatcherPtr) *children;
|
klist_t(WatcherPtr) *children;
|
||||||
uv_signal_t children_watcher;
|
uv_signal_t children_watcher;
|
||||||
uv_timer_t children_kill_timer, poll_timer;
|
uv_timer_t children_kill_timer, poll_timer;
|
||||||
size_t children_stop_requests;
|
size_t children_stop_requests;
|
||||||
|
uv_async_t async;
|
||||||
|
uv_mutex_t mutex;
|
||||||
|
int recursive;
|
||||||
} Loop;
|
} Loop;
|
||||||
|
|
||||||
#define CREATE_EVENT(queue, handler, argc, ...) \
|
#define CREATE_EVENT(queue, handler, argc, ...) \
|
||||||
|
@ -105,16 +105,15 @@ static Queue *queue_new(Queue *parent, put_callback put_cb, void *data)
|
|||||||
void queue_free(Queue *queue)
|
void queue_free(Queue *queue)
|
||||||
{
|
{
|
||||||
assert(queue);
|
assert(queue);
|
||||||
if (queue->parent) {
|
while (!QUEUE_EMPTY(&queue->headtail)) {
|
||||||
while (!QUEUE_EMPTY(&queue->headtail)) {
|
QUEUE *q = QUEUE_HEAD(&queue->headtail);
|
||||||
QUEUE *q = QUEUE_HEAD(&queue->headtail);
|
QueueItem *item = queue_node_data(q);
|
||||||
QueueItem *item = queue_node_data(q);
|
if (queue->parent) {
|
||||||
assert(!item->link);
|
|
||||||
QUEUE_REMOVE(&item->data.item.parent->node);
|
QUEUE_REMOVE(&item->data.item.parent->node);
|
||||||
xfree(item->data.item.parent);
|
xfree(item->data.item.parent);
|
||||||
QUEUE_REMOVE(q);
|
|
||||||
xfree(item);
|
|
||||||
}
|
}
|
||||||
|
QUEUE_REMOVE(q);
|
||||||
|
xfree(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
xfree(queue);
|
xfree(queue);
|
||||||
@ -128,9 +127,8 @@ Event queue_get(Queue *queue)
|
|||||||
void queue_put_event(Queue *queue, Event event)
|
void queue_put_event(Queue *queue, Event event)
|
||||||
{
|
{
|
||||||
assert(queue);
|
assert(queue);
|
||||||
assert(queue->parent); // don't push directly to the parent queue
|
|
||||||
queue_push(queue, event);
|
queue_push(queue, event);
|
||||||
if (queue->parent->put_cb) {
|
if (queue->parent && queue->parent->put_cb) {
|
||||||
queue->parent->put_cb(queue->parent, queue->parent->data);
|
queue->parent->put_cb(queue->parent, queue->parent->data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,11 +175,11 @@ static Event queue_remove(Queue *queue)
|
|||||||
rv = child->data.item.event;
|
rv = child->data.item.event;
|
||||||
xfree(child);
|
xfree(child);
|
||||||
} else {
|
} else {
|
||||||
assert(queue->parent);
|
if (queue->parent) {
|
||||||
assert(!queue_empty(queue->parent));
|
// remove the corresponding link node in the parent queue
|
||||||
// remove the corresponding link node in the parent queue
|
QUEUE_REMOVE(&item->data.item.parent->node);
|
||||||
QUEUE_REMOVE(&item->data.item.parent->node);
|
xfree(item->data.item.parent);
|
||||||
xfree(item->data.item.parent);
|
}
|
||||||
rv = item->data.item.event;
|
rv = item->data.item.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,11 +193,13 @@ static void queue_push(Queue *queue, Event event)
|
|||||||
item->link = false;
|
item->link = false;
|
||||||
item->data.item.event = event;
|
item->data.item.event = event;
|
||||||
QUEUE_INSERT_TAIL(&queue->headtail, &item->node);
|
QUEUE_INSERT_TAIL(&queue->headtail, &item->node);
|
||||||
// push link node to the parent queue
|
if (queue->parent) {
|
||||||
item->data.item.parent = xmalloc(sizeof(QueueItem));
|
// push link node to the parent queue
|
||||||
item->data.item.parent->link = true;
|
item->data.item.parent = xmalloc(sizeof(QueueItem));
|
||||||
item->data.item.parent->data.queue = queue;
|
item->data.item.parent->link = true;
|
||||||
QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
|
item->data.item.parent->data.queue = queue;
|
||||||
|
QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static QueueItem *queue_node_data(QUEUE *q)
|
static QueueItem *queue_node_data(QUEUE *q)
|
||||||
|
@ -16,29 +16,49 @@
|
|||||||
|
|
||||||
#define USR_LOG_FILE "$HOME/.nvimlog"
|
#define USR_LOG_FILE "$HOME/.nvimlog"
|
||||||
|
|
||||||
|
static uv_mutex_t mutex;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "log.c.generated.h"
|
# include "log.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void log_init(void)
|
||||||
|
{
|
||||||
|
uv_mutex_init(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_lock(void)
|
||||||
|
{
|
||||||
|
uv_mutex_lock(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_unlock(void)
|
||||||
|
{
|
||||||
|
uv_mutex_unlock(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
bool do_log(int log_level, const char *func_name, int line_num, bool eol,
|
bool do_log(int log_level, const char *func_name, int line_num, bool eol,
|
||||||
const char* fmt, ...) FUNC_ATTR_UNUSED
|
const char* fmt, ...) FUNC_ATTR_UNUSED
|
||||||
{
|
{
|
||||||
|
log_lock();
|
||||||
|
bool ret = false;
|
||||||
FILE *log_file = open_log_file();
|
FILE *log_file = open_log_file();
|
||||||
|
|
||||||
if (log_file == NULL) {
|
if (log_file == NULL) {
|
||||||
return false;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
|
ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol,
|
||||||
fmt, args);
|
fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
|
||||||
if (log_file != stderr && log_file != stdout) {
|
if (log_file != stderr && log_file != stdout) {
|
||||||
fclose(log_file);
|
fclose(log_file);
|
||||||
}
|
}
|
||||||
|
end:
|
||||||
|
log_unlock();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +177,7 @@ void event_teardown(void)
|
|||||||
/// Needed for unit tests. Must be called after `time_init()`.
|
/// Needed for unit tests. Must be called after `time_init()`.
|
||||||
void early_init(void)
|
void early_init(void)
|
||||||
{
|
{
|
||||||
|
log_init();
|
||||||
fs_init();
|
fs_init();
|
||||||
handle_init();
|
handle_init();
|
||||||
|
|
||||||
|
@ -825,6 +825,7 @@ static void log_server_msg(uint64_t channel_id,
|
|||||||
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
|
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
|
||||||
uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
|
uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
|
||||||
DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
|
DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
|
||||||
|
log_lock();
|
||||||
FILE *f = open_log_file();
|
FILE *f = open_log_file();
|
||||||
fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
|
fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
|
||||||
log_msg_close(f, unpacked.data);
|
log_msg_close(f, unpacked.data);
|
||||||
@ -836,6 +837,7 @@ static void log_client_msg(uint64_t channel_id,
|
|||||||
msgpack_object msg)
|
msgpack_object msg)
|
||||||
{
|
{
|
||||||
DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
|
DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
|
||||||
|
log_lock();
|
||||||
FILE *f = open_log_file();
|
FILE *f = open_log_file();
|
||||||
fprintf(f, is_request ? REQ : RES);
|
fprintf(f, is_request ? REQ : RES);
|
||||||
log_msg_close(f, msg);
|
log_msg_close(f, msg);
|
||||||
@ -847,6 +849,7 @@ static void log_msg_close(FILE *f, msgpack_object msg)
|
|||||||
fputc('\n', f);
|
fputc('\n', f);
|
||||||
fflush(f);
|
fflush(f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
log_unlock();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,21 +1,86 @@
|
|||||||
#include <termkey.h>
|
|
||||||
|
|
||||||
|
#include "nvim/tui/input.h"
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/api/vim.h"
|
||||||
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/misc2.h"
|
#include "nvim/misc2.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/event/rstream.h"
|
#include "nvim/event/rstream.h"
|
||||||
#include "nvim/event/time.h"
|
|
||||||
|
|
||||||
#define PASTETOGGLE_KEY "<f37>"
|
#define PASTETOGGLE_KEY "<f37>"
|
||||||
|
|
||||||
struct term_input {
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
int in_fd;
|
# include "tui/input.c.generated.h"
|
||||||
bool paste_enabled;
|
#endif
|
||||||
TermKey *tk;
|
|
||||||
TimeWatcher timer_handle;
|
void term_input_init(TermInput *input, Loop *loop)
|
||||||
Stream read_stream;
|
{
|
||||||
};
|
input->loop = loop;
|
||||||
|
input->paste_enabled = false;
|
||||||
|
input->in_fd = 0;
|
||||||
|
|
||||||
|
const char *term = os_getenv("TERM");
|
||||||
|
if (!term) {
|
||||||
|
term = ""; // termkey_new_abstract assumes non-null (#2745)
|
||||||
|
}
|
||||||
|
input->tk = termkey_new_abstract(term, 0);
|
||||||
|
int curflags = termkey_get_canonflags(input->tk);
|
||||||
|
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
|
||||||
|
// setup input handle
|
||||||
|
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff, input);
|
||||||
|
// initialize a timer handle for handling ESC with libtermkey
|
||||||
|
time_watcher_init(loop, &input->timer_handle, input);
|
||||||
|
// Set the pastetoggle option to a special key that will be sent when
|
||||||
|
// \e[20{0,1}~/ are received
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
vim_set_option(cstr_as_string("pastetoggle"),
|
||||||
|
STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void term_input_destroy(TermInput *input)
|
||||||
|
{
|
||||||
|
time_watcher_close(&input->timer_handle, NULL);
|
||||||
|
stream_close(&input->read_stream, NULL);
|
||||||
|
termkey_destroy(input->tk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void term_input_start(TermInput *input)
|
||||||
|
{
|
||||||
|
rstream_start(&input->read_stream, read_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void term_input_stop(TermInput *input)
|
||||||
|
{
|
||||||
|
rstream_stop(&input->read_stream);
|
||||||
|
time_watcher_stop(&input->timer_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void term_input_set_encoding(TermInput *input, char* enc)
|
||||||
|
{
|
||||||
|
int enc_flag = strcmp(enc, "utf-8") == 0 ? TERMKEY_FLAG_UTF8
|
||||||
|
: TERMKEY_FLAG_RAW;
|
||||||
|
termkey_set_flags(input->tk, enc_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_enqueue_event(void **argv)
|
||||||
|
{
|
||||||
|
char *buf = argv[0];
|
||||||
|
input_enqueue(cstr_as_string(buf));
|
||||||
|
xfree(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_done_event(void **argv)
|
||||||
|
{
|
||||||
|
input_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void enqueue_input(char *buf, size_t size)
|
||||||
|
{
|
||||||
|
loop_schedule(&loop, event_create(1, input_enqueue_event, 1,
|
||||||
|
xstrndup(buf, size)));
|
||||||
|
}
|
||||||
|
|
||||||
static void forward_simple_utf8(TermKeyKey *key)
|
static void forward_simple_utf8(TermKeyKey *key)
|
||||||
{
|
{
|
||||||
@ -33,7 +98,7 @@ static void forward_simple_utf8(TermKeyKey *key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf[len] = 0;
|
buf[len] = 0;
|
||||||
input_enqueue((String){.data = buf, .size = len});
|
enqueue_input(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
|
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
|
||||||
@ -48,7 +113,7 @@ static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
|
|||||||
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
|
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
input_enqueue((String){.data = buf, .size = len});
|
enqueue_input(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
|
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
|
||||||
@ -99,7 +164,7 @@ static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
|
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
|
||||||
input_enqueue((String){.data = buf, .size = len});
|
enqueue_input(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
|
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
|
||||||
@ -175,16 +240,16 @@ static bool handle_bracketed_paste(TermInput *input)
|
|||||||
int state = get_real_state();
|
int state = get_real_state();
|
||||||
if (state & NORMAL) {
|
if (state & NORMAL) {
|
||||||
// Enter insert mode
|
// Enter insert mode
|
||||||
input_enqueue(cstr_as_string("i"));
|
enqueue_input("i", 1);
|
||||||
} else if (state & VISUAL) {
|
} else if (state & VISUAL) {
|
||||||
// Remove the selected text and enter insert mode
|
// Remove the selected text and enter insert mode
|
||||||
input_enqueue(cstr_as_string("c"));
|
enqueue_input("c", 1);
|
||||||
} else if (!(state & INSERT)) {
|
} else if (!(state & INSERT)) {
|
||||||
// Don't mess with the paste option
|
// Don't mess with the paste option
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input_enqueue(cstr_as_string(PASTETOGGLE_KEY));
|
enqueue_input(PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
|
||||||
input->paste_enabled = enable;
|
input->paste_enabled = enable;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -227,9 +292,9 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
|
|||||||
// ls *.md | xargs nvim
|
// ls *.md | xargs nvim
|
||||||
input->in_fd = 2;
|
input->in_fd = 2;
|
||||||
stream_close(&input->read_stream, NULL);
|
stream_close(&input->read_stream, NULL);
|
||||||
queue_put(loop.fast_events, restart_reading, 1, input);
|
queue_put(input->loop->fast_events, restart_reading, 1, input);
|
||||||
} else {
|
} else {
|
||||||
input_done();
|
loop_schedule(&loop, event_create(1, input_done_event, 0));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -275,51 +340,6 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
|
|||||||
static void restart_reading(void **argv)
|
static void restart_reading(void **argv)
|
||||||
{
|
{
|
||||||
TermInput *input = argv[0];
|
TermInput *input = argv[0];
|
||||||
rstream_init_fd(&loop, &input->read_stream, input->in_fd, 0xfff, input);
|
rstream_init_fd(input->loop, &input->read_stream, input->in_fd, 0xfff, input);
|
||||||
rstream_start(&input->read_stream, read_cb);
|
rstream_start(&input->read_stream, read_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static TermInput *term_input_new(void)
|
|
||||||
{
|
|
||||||
TermInput *rv = xmalloc(sizeof(TermInput));
|
|
||||||
rv->paste_enabled = false;
|
|
||||||
rv->in_fd = 0;
|
|
||||||
|
|
||||||
const char *term = os_getenv("TERM");
|
|
||||||
if (!term) {
|
|
||||||
term = ""; // termkey_new_abstract assumes non-null (#2745)
|
|
||||||
}
|
|
||||||
rv->tk = termkey_new_abstract(term, 0);
|
|
||||||
int curflags = termkey_get_canonflags(rv->tk);
|
|
||||||
termkey_set_canonflags(rv->tk, curflags | TERMKEY_CANON_DELBS);
|
|
||||||
// setup input handle
|
|
||||||
rstream_init_fd(&loop, &rv->read_stream, rv->in_fd, 0xfff, rv);
|
|
||||||
rstream_start(&rv->read_stream, read_cb);
|
|
||||||
// initialize a timer handle for handling ESC with libtermkey
|
|
||||||
time_watcher_init(&loop, &rv->timer_handle, rv);
|
|
||||||
// Set the pastetoggle option to a special key that will be sent when
|
|
||||||
// \e[20{0,1}~/ are received
|
|
||||||
Error err = ERROR_INIT;
|
|
||||||
vim_set_option(cstr_as_string("pastetoggle"),
|
|
||||||
STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void term_input_destroy(TermInput *input)
|
|
||||||
{
|
|
||||||
time_watcher_stop(&input->timer_handle);
|
|
||||||
time_watcher_close(&input->timer_handle, NULL);
|
|
||||||
rstream_stop(&input->read_stream);
|
|
||||||
stream_close(&input->read_stream, NULL);
|
|
||||||
termkey_destroy(input->tk);
|
|
||||||
// Run once to remove references to input/timer handles
|
|
||||||
loop_poll_events(&loop, 0);
|
|
||||||
xfree(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void term_input_set_encoding(TermInput *input, char* enc)
|
|
||||||
{
|
|
||||||
int enc_flag = strcmp(enc, "utf-8") == 0 ? TERMKEY_FLAG_UTF8
|
|
||||||
: TERMKEY_FLAG_RAW;
|
|
||||||
termkey_set_flags(input->tk, enc_flag);
|
|
||||||
}
|
|
23
src/nvim/tui/input.h
Normal file
23
src/nvim/tui/input.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef NVIM_TUI_INPUT_H
|
||||||
|
#define NVIM_TUI_INPUT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <termkey.h>
|
||||||
|
#include "nvim/event/stream.h"
|
||||||
|
#include "nvim/event/time.h"
|
||||||
|
|
||||||
|
typedef struct term_input {
|
||||||
|
int in_fd;
|
||||||
|
bool paste_enabled;
|
||||||
|
TermKey *tk;
|
||||||
|
TimeWatcher timer_handle;
|
||||||
|
Loop *loop;
|
||||||
|
Stream read_stream;
|
||||||
|
} TermInput;
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "tui/input.h.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // NVIM_TUI_INPUT_H
|
@ -17,46 +17,45 @@
|
|||||||
#include "nvim/event/loop.h"
|
#include "nvim/event/loop.h"
|
||||||
#include "nvim/event/signal.h"
|
#include "nvim/event/signal.h"
|
||||||
#include "nvim/tui/tui.h"
|
#include "nvim/tui/tui.h"
|
||||||
|
#include "nvim/tui/input.h"
|
||||||
|
#include "nvim/os/input.h"
|
||||||
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
|
#include "nvim/ugrid.h"
|
||||||
|
#include "nvim/ui_bridge.h"
|
||||||
|
|
||||||
// Space reserved in the output buffer to restore the cursor to normal when
|
// Space reserved in the output buffer to restore the cursor to normal when
|
||||||
// flushing. No existing terminal will require 32 bytes to do that.
|
// flushing. No existing terminal will require 32 bytes to do that.
|
||||||
#define CNORM_COMMAND_MAX_SIZE 32
|
#define CNORM_COMMAND_MAX_SIZE 32
|
||||||
#define OUTBUF_SIZE 0xffff
|
#define OUTBUF_SIZE 0xffff
|
||||||
|
|
||||||
typedef struct term_input TermInput;
|
|
||||||
|
|
||||||
#include "term_input.inl"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int top, bot, left, right;
|
int top, bot, left, right;
|
||||||
} Rect;
|
} Rect;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char data[7];
|
UIBridgeData *bridge;
|
||||||
HlAttrs attrs;
|
Loop *loop;
|
||||||
} Cell;
|
bool stop;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
unibi_var_t params[9];
|
unibi_var_t params[9];
|
||||||
char buf[OUTBUF_SIZE];
|
char buf[OUTBUF_SIZE];
|
||||||
size_t bufpos, bufsize;
|
size_t bufpos, bufsize;
|
||||||
TermInput *input;
|
TermInput input;
|
||||||
uv_loop_t *write_loop;
|
uv_loop_t write_loop;
|
||||||
unibi_term *ut;
|
unibi_term *ut;
|
||||||
uv_tty_t output_handle;
|
uv_tty_t output_handle;
|
||||||
SignalWatcher winch_handle;
|
SignalWatcher winch_handle, cont_handle;
|
||||||
Rect scroll_region;
|
bool cont_received;
|
||||||
|
// Event scheduled by the ui bridge. Since the main thread suspends until
|
||||||
|
// the event is handled, it is fine to use a single field instead of a queue
|
||||||
|
Event scheduled_event;
|
||||||
|
UGrid grid;
|
||||||
kvec_t(Rect) invalid_regions;
|
kvec_t(Rect) invalid_regions;
|
||||||
int row, col;
|
|
||||||
int bg, fg;
|
|
||||||
int out_fd;
|
int out_fd;
|
||||||
int old_height;
|
|
||||||
bool can_use_terminal_scroll;
|
bool can_use_terminal_scroll;
|
||||||
bool mouse_enabled;
|
bool mouse_enabled;
|
||||||
bool busy;
|
bool busy;
|
||||||
HlAttrs attrs, print_attrs;
|
HlAttrs print_attrs;
|
||||||
Cell **screen;
|
|
||||||
int showing_mode;
|
int showing_mode;
|
||||||
struct {
|
struct {
|
||||||
int enable_mouse, disable_mouse;
|
int enable_mouse, disable_mouse;
|
||||||
@ -72,80 +71,12 @@ static bool volatile got_winch = false;
|
|||||||
# include "tui/tui.c.generated.h"
|
# include "tui/tui.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
|
|
||||||
|
|
||||||
#define FOREACH_CELL(ui, top, bot, left, right, go, code) \
|
|
||||||
do { \
|
|
||||||
TUIData *data = ui->data; \
|
|
||||||
for (int row = top; row <= bot; ++row) { \
|
|
||||||
Cell *cells = data->screen[row]; \
|
|
||||||
if (go) { \
|
|
||||||
unibi_goto(ui, row, left); \
|
|
||||||
} \
|
|
||||||
for (int col = left; col <= right; ++col) { \
|
|
||||||
Cell *cell = cells + col; \
|
|
||||||
(void)(cell); \
|
|
||||||
code; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
|
|
||||||
UI *tui_start(void)
|
UI *tui_start(void)
|
||||||
{
|
{
|
||||||
TUIData *data = xcalloc(1, sizeof(TUIData));
|
|
||||||
UI *ui = xcalloc(1, sizeof(UI));
|
UI *ui = xcalloc(1, sizeof(UI));
|
||||||
ui->data = data;
|
|
||||||
data->attrs = data->print_attrs = EMPTY_ATTRS;
|
|
||||||
data->fg = data->bg = -1;
|
|
||||||
data->can_use_terminal_scroll = true;
|
|
||||||
data->bufpos = 0;
|
|
||||||
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
|
|
||||||
data->showing_mode = 0;
|
|
||||||
data->unibi_ext.enable_mouse = -1;
|
|
||||||
data->unibi_ext.disable_mouse = -1;
|
|
||||||
data->unibi_ext.enable_bracketed_paste = -1;
|
|
||||||
data->unibi_ext.disable_bracketed_paste = -1;
|
|
||||||
data->unibi_ext.enter_insert_mode = -1;
|
|
||||||
data->unibi_ext.enter_replace_mode = -1;
|
|
||||||
data->unibi_ext.exit_insert_mode = -1;
|
|
||||||
|
|
||||||
// write output to stderr if stdout is not a tty
|
|
||||||
data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
|
|
||||||
kv_init(data->invalid_regions);
|
|
||||||
// setup term input
|
|
||||||
data->input = term_input_new();
|
|
||||||
// setup unibilium
|
|
||||||
data->ut = unibi_from_env();
|
|
||||||
if (!data->ut) {
|
|
||||||
// For some reason could not read terminfo file, use a dummy entry that
|
|
||||||
// will be populated with common values by fix_terminfo below
|
|
||||||
data->ut = unibi_dummy();
|
|
||||||
}
|
|
||||||
fix_terminfo(data);
|
|
||||||
// Enter alternate screen and clear
|
|
||||||
unibi_out(ui, unibi_enter_ca_mode);
|
|
||||||
unibi_out(ui, unibi_clear_screen);
|
|
||||||
// Enable bracketed paste
|
|
||||||
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
|
|
||||||
// setup output handle in a separate event loop(we wanna do synchronous
|
|
||||||
// write to the tty)
|
|
||||||
data->write_loop = xmalloc(sizeof(uv_loop_t));
|
|
||||||
uv_loop_init(data->write_loop);
|
|
||||||
uv_tty_init(data->write_loop, &data->output_handle, data->out_fd, 0);
|
|
||||||
uv_tty_set_mode(&data->output_handle, UV_TTY_MODE_RAW);
|
|
||||||
|
|
||||||
// Obtain screen dimensions
|
|
||||||
update_size(ui);
|
|
||||||
|
|
||||||
// listen for SIGWINCH
|
|
||||||
signal_watcher_init(&loop, &data->winch_handle, ui);
|
|
||||||
data->winch_handle.events = queue_new_child(loop.events);
|
|
||||||
signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
|
|
||||||
|
|
||||||
ui->stop = tui_stop;
|
ui->stop = tui_stop;
|
||||||
ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
|
ui->rgb = os_getenv("NVIM_TUI_ENABLE_TRUE_COLOR") != NULL;
|
||||||
ui->data = data;
|
|
||||||
ui->resize = tui_resize;
|
ui->resize = tui_resize;
|
||||||
ui->clear = tui_clear;
|
ui->clear = tui_clear;
|
||||||
ui->eol_clear = tui_eol_clear;
|
ui->eol_clear = tui_eol_clear;
|
||||||
@ -169,21 +100,46 @@ UI *tui_start(void)
|
|||||||
ui->set_title = tui_set_title;
|
ui->set_title = tui_set_title;
|
||||||
ui->set_icon = tui_set_icon;
|
ui->set_icon = tui_set_icon;
|
||||||
ui->set_encoding = tui_set_encoding;
|
ui->set_encoding = tui_set_encoding;
|
||||||
// Attach
|
return ui_bridge_attach(ui, tui_main, tui_scheduler);
|
||||||
ui_attach(ui);
|
|
||||||
return ui;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_stop(UI *ui)
|
static void terminfo_start(UI *ui)
|
||||||
|
{
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
data->can_use_terminal_scroll = true;
|
||||||
|
data->bufpos = 0;
|
||||||
|
data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE;
|
||||||
|
data->showing_mode = 0;
|
||||||
|
data->unibi_ext.enable_mouse = -1;
|
||||||
|
data->unibi_ext.disable_mouse = -1;
|
||||||
|
data->unibi_ext.enable_bracketed_paste = -1;
|
||||||
|
data->unibi_ext.disable_bracketed_paste = -1;
|
||||||
|
data->unibi_ext.enter_insert_mode = -1;
|
||||||
|
data->unibi_ext.enter_replace_mode = -1;
|
||||||
|
data->unibi_ext.exit_insert_mode = -1;
|
||||||
|
// write output to stderr if stdout is not a tty
|
||||||
|
data->out_fd = os_isatty(1) ? 1 : (os_isatty(2) ? 2 : 1);
|
||||||
|
// setup unibilium
|
||||||
|
data->ut = unibi_from_env();
|
||||||
|
if (!data->ut) {
|
||||||
|
// For some reason could not read terminfo file, use a dummy entry that
|
||||||
|
// will be populated with common values by fix_terminfo below
|
||||||
|
data->ut = unibi_dummy();
|
||||||
|
}
|
||||||
|
fix_terminfo(data);
|
||||||
|
// Enter alternate screen and clear
|
||||||
|
unibi_out(ui, unibi_enter_ca_mode);
|
||||||
|
unibi_out(ui, unibi_clear_screen);
|
||||||
|
// Enable bracketed paste
|
||||||
|
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
|
||||||
|
uv_loop_init(&data->write_loop);
|
||||||
|
uv_tty_init(&data->write_loop, &data->output_handle, data->out_fd, 0);
|
||||||
|
uv_tty_set_mode(&data->output_handle, UV_TTY_MODE_RAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void terminfo_stop(UI *ui)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
// Destroy common stuff
|
|
||||||
kv_destroy(data->invalid_regions);
|
|
||||||
signal_watcher_stop(&data->winch_handle);
|
|
||||||
queue_free(data->winch_handle.events);
|
|
||||||
signal_watcher_close(&data->winch_handle, NULL);
|
|
||||||
// Destroy input stuff
|
|
||||||
term_input_destroy(data->input);
|
|
||||||
// Destroy output stuff
|
// Destroy output stuff
|
||||||
tui_mode_change(ui, NORMAL);
|
tui_mode_change(ui, NORMAL);
|
||||||
tui_mouse_off(ui);
|
tui_mouse_off(ui);
|
||||||
@ -196,24 +152,99 @@ static void tui_stop(UI *ui)
|
|||||||
flush_buf(ui);
|
flush_buf(ui);
|
||||||
uv_tty_reset_mode();
|
uv_tty_reset_mode();
|
||||||
uv_close((uv_handle_t *)&data->output_handle, NULL);
|
uv_close((uv_handle_t *)&data->output_handle, NULL);
|
||||||
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
uv_run(&data->write_loop, UV_RUN_DEFAULT);
|
||||||
if (uv_loop_close(data->write_loop)) {
|
if (uv_loop_close(&data->write_loop)) {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
xfree(data->write_loop);
|
|
||||||
unibi_destroy(data->ut);
|
unibi_destroy(data->ut);
|
||||||
destroy_screen(data);
|
}
|
||||||
|
|
||||||
|
static void tui_terminal_start(UI *ui)
|
||||||
|
{
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
data->print_attrs = EMPTY_ATTRS;
|
||||||
|
ugrid_init(&data->grid);
|
||||||
|
terminfo_start(ui);
|
||||||
|
update_size(ui);
|
||||||
|
signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);
|
||||||
|
term_input_start(&data->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tui_terminal_stop(UI *ui)
|
||||||
|
{
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
term_input_stop(&data->input);
|
||||||
|
signal_watcher_stop(&data->winch_handle);
|
||||||
|
terminfo_stop(ui);
|
||||||
|
ugrid_free(&data->grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tui_stop(UI *ui)
|
||||||
|
{
|
||||||
|
tui_terminal_stop(ui);
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
data->stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main function of the TUI thread
|
||||||
|
static void tui_main(UIBridgeData *bridge, UI *ui)
|
||||||
|
{
|
||||||
|
Loop tui_loop;
|
||||||
|
loop_init(&tui_loop, NULL);
|
||||||
|
TUIData *data = xcalloc(1, sizeof(TUIData));
|
||||||
|
ui->data = data;
|
||||||
|
data->bridge = bridge;
|
||||||
|
data->loop = &tui_loop;
|
||||||
|
kv_init(data->invalid_regions);
|
||||||
|
signal_watcher_init(data->loop, &data->winch_handle, ui);
|
||||||
|
signal_watcher_init(data->loop, &data->cont_handle, data);
|
||||||
|
signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);
|
||||||
|
// initialize input reading structures
|
||||||
|
term_input_init(&data->input, &tui_loop);
|
||||||
|
tui_terminal_start(ui);
|
||||||
|
data->stop = false;
|
||||||
|
// allow the main thread to continue, we are ready to start handling UI
|
||||||
|
// callbacks
|
||||||
|
CONTINUE(bridge);
|
||||||
|
|
||||||
|
while (!data->stop) {
|
||||||
|
loop_poll_events(&tui_loop, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
term_input_destroy(&data->input);
|
||||||
|
signal_watcher_stop(&data->cont_handle);
|
||||||
|
signal_watcher_close(&data->cont_handle, NULL);
|
||||||
|
signal_watcher_close(&data->winch_handle, NULL);
|
||||||
|
loop_close(&tui_loop);
|
||||||
|
kv_destroy(data->invalid_regions);
|
||||||
xfree(data);
|
xfree(data);
|
||||||
ui_detach(ui);
|
|
||||||
xfree(ui);
|
xfree(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void tui_scheduler(Event event, void *d)
|
||||||
|
{
|
||||||
|
UI *ui = d;
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
loop_schedule(data->loop, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_event(void **argv)
|
||||||
|
{
|
||||||
|
ui_refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sigcont_cb(SignalWatcher *watcher, int signum, void *data)
|
||||||
|
{
|
||||||
|
((TUIData *)data)->cont_received = true;
|
||||||
|
}
|
||||||
|
|
||||||
static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
|
static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
|
||||||
{
|
{
|
||||||
got_winch = true;
|
got_winch = true;
|
||||||
UI *ui = data;
|
UI *ui = data;
|
||||||
update_size(ui);
|
update_size(ui);
|
||||||
ui_refresh();
|
// run refresh_event in nvim main loop
|
||||||
|
loop_schedule(&loop, event_create(1, refresh_event, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
|
static bool attrs_differ(HlAttrs a1, HlAttrs a2)
|
||||||
@ -234,9 +265,10 @@ static void update_attrs(UI *ui, HlAttrs attrs)
|
|||||||
|
|
||||||
data->print_attrs = attrs;
|
data->print_attrs = attrs;
|
||||||
unibi_out(ui, unibi_exit_attribute_mode);
|
unibi_out(ui, unibi_exit_attribute_mode);
|
||||||
|
UGrid *grid = &data->grid;
|
||||||
|
|
||||||
int fg = attrs.foreground != -1 ? attrs.foreground : data->fg;
|
int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg;
|
||||||
int bg = attrs.background != -1 ? attrs.background : data->bg;
|
int bg = attrs.background != -1 ? attrs.background : grid->bg;
|
||||||
|
|
||||||
if (ui->rgb) {
|
if (ui->rgb) {
|
||||||
if (fg != -1) {
|
if (fg != -1) {
|
||||||
@ -278,25 +310,25 @@ static void update_attrs(UI *ui, HlAttrs attrs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_cell(UI *ui, Cell *ptr)
|
static void print_cell(UI *ui, UCell *ptr)
|
||||||
{
|
{
|
||||||
update_attrs(ui, ptr->attrs);
|
update_attrs(ui, ptr->attrs);
|
||||||
out(ui, ptr->data, strlen(ptr->data));
|
out(ui, ptr->data, strlen(ptr->data));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clear_region(UI *ui, int top, int bot, int left, int right,
|
static void clear_region(UI *ui, int top, int bot, int left, int right)
|
||||||
bool refresh)
|
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
HlAttrs clear_attrs = EMPTY_ATTRS;
|
UGrid *grid = &data->grid;
|
||||||
clear_attrs.foreground = data->fg;
|
|
||||||
clear_attrs.background = data->bg;
|
|
||||||
update_attrs(ui, clear_attrs);
|
|
||||||
|
|
||||||
bool cleared = false;
|
bool cleared = false;
|
||||||
if (refresh && data->bg == -1 && right == ui->width -1) {
|
if (grid->bg == -1 && right == ui->width -1) {
|
||||||
// Background is set to the default color and the right edge matches the
|
// Background is set to the default color and the right edge matches the
|
||||||
// screen end, try to use terminal codes for clearing the requested area.
|
// screen end, try to use terminal codes for clearing the requested area.
|
||||||
|
HlAttrs clear_attrs = EMPTY_ATTRS;
|
||||||
|
clear_attrs.foreground = grid->fg;
|
||||||
|
clear_attrs.background = grid->bg;
|
||||||
|
update_attrs(ui, clear_attrs);
|
||||||
if (left == 0) {
|
if (left == 0) {
|
||||||
if (bot == ui->height - 1) {
|
if (bot == ui->height - 1) {
|
||||||
if (top == 0) {
|
if (top == 0) {
|
||||||
@ -319,36 +351,26 @@ static void clear_region(UI *ui, int top, int bot, int left, int right,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool clear = refresh && !cleared;
|
if (!cleared) {
|
||||||
FOREACH_CELL(ui, top, bot, left, right, clear, {
|
// could not clear using faster terminal codes, refresh the whole region
|
||||||
cell->data[0] = ' ';
|
int currow = -1;
|
||||||
cell->data[1] = 0;
|
UGRID_FOREACH_CELL(grid, top, bot, left, right, {
|
||||||
cell->attrs = clear_attrs;
|
if (currow != row) {
|
||||||
if (clear) {
|
unibi_goto(ui, row, col);
|
||||||
|
currow = row;
|
||||||
|
}
|
||||||
print_cell(ui, cell);
|
print_cell(ui, cell);
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// restore cursor
|
// restore cursor
|
||||||
unibi_goto(ui, data->row, data->col);
|
unibi_goto(ui, grid->row, grid->col);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_resize(UI *ui, int width, int height)
|
static void tui_resize(UI *ui, int width, int height)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
destroy_screen(data);
|
ugrid_resize(&data->grid, width, height);
|
||||||
|
|
||||||
data->screen = xmalloc((size_t)height * sizeof(Cell *));
|
|
||||||
for (int i = 0; i < height; i++) {
|
|
||||||
data->screen[i] = xcalloc((size_t)width, sizeof(Cell));
|
|
||||||
}
|
|
||||||
|
|
||||||
data->old_height = height;
|
|
||||||
data->scroll_region.top = 0;
|
|
||||||
data->scroll_region.bot = height - 1;
|
|
||||||
data->scroll_region.left = 0;
|
|
||||||
data->scroll_region.right = width - 1;
|
|
||||||
data->row = data->col = 0;
|
|
||||||
|
|
||||||
if (!got_winch) { // Try to resize the terminal window.
|
if (!got_winch) { // Try to resize the terminal window.
|
||||||
char r[16]; // enough for 9999x9999
|
char r[16]; // enough for 9999x9999
|
||||||
@ -362,22 +384,23 @@ static void tui_resize(UI *ui, int width, int height)
|
|||||||
static void tui_clear(UI *ui)
|
static void tui_clear(UI *ui)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
clear_region(ui, data->scroll_region.top, data->scroll_region.bot,
|
UGrid *grid = &data->grid;
|
||||||
data->scroll_region.left, data->scroll_region.right, true);
|
ugrid_clear(grid);
|
||||||
|
clear_region(ui, grid->top, grid->bot, grid->left, grid->right);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_eol_clear(UI *ui)
|
static void tui_eol_clear(UI *ui)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
clear_region(ui, data->row, data->row, data->col,
|
UGrid *grid = &data->grid;
|
||||||
data->scroll_region.right, true);
|
ugrid_eol_clear(grid);
|
||||||
|
clear_region(ui, grid->row, grid->row, grid->col, grid->right);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_cursor_goto(UI *ui, int row, int col)
|
static void tui_cursor_goto(UI *ui, int row, int col)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
data->row = row;
|
ugrid_goto(&data->grid, row, col);
|
||||||
data->col = col;
|
|
||||||
unibi_goto(ui, row, col);
|
unibi_goto(ui, row, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,11 +458,7 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
|
|||||||
int right)
|
int right)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
data->scroll_region.top = top;
|
ugrid_set_scroll_region(&data->grid, top, bot, left, right);
|
||||||
data->scroll_region.bot = bot;
|
|
||||||
data->scroll_region.left = left;
|
|
||||||
data->scroll_region.right = right;
|
|
||||||
|
|
||||||
data->can_use_terminal_scroll =
|
data->can_use_terminal_scroll =
|
||||||
left == 0 && right == ui->width - 1
|
left == 0 && right == ui->width - 1
|
||||||
&& ((top == 0 && bot == ui->height - 1)
|
&& ((top == 0 && bot == ui->height - 1)
|
||||||
@ -449,31 +468,24 @@ static void tui_set_scroll_region(UI *ui, int top, int bot, int left,
|
|||||||
static void tui_scroll(UI *ui, int count)
|
static void tui_scroll(UI *ui, int count)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
int top = data->scroll_region.top;
|
UGrid *grid = &data->grid;
|
||||||
int bot = data->scroll_region.bot;
|
int clear_top, clear_bot;
|
||||||
int left = data->scroll_region.left;
|
ugrid_scroll(grid, count, &clear_top, &clear_bot);
|
||||||
int right = data->scroll_region.right;
|
|
||||||
|
|
||||||
if (data->can_use_terminal_scroll) {
|
if (data->can_use_terminal_scroll) {
|
||||||
// Change terminal scroll region and move cursor to the top
|
// Change terminal scroll region and move cursor to the top
|
||||||
data->params[0].i = top;
|
data->params[0].i = grid->top;
|
||||||
data->params[1].i = bot;
|
data->params[1].i = grid->bot;
|
||||||
unibi_out(ui, unibi_change_scroll_region);
|
unibi_out(ui, unibi_change_scroll_region);
|
||||||
unibi_goto(ui, top, left);
|
unibi_goto(ui, grid->top, grid->left);
|
||||||
// also set default color attributes or some terminals can become funny
|
// also set default color attributes or some terminals can become funny
|
||||||
HlAttrs clear_attrs = EMPTY_ATTRS;
|
HlAttrs clear_attrs = EMPTY_ATTRS;
|
||||||
clear_attrs.foreground = data->fg;
|
clear_attrs.foreground = grid->fg;
|
||||||
clear_attrs.background = data->bg;
|
clear_attrs.background = grid->bg;
|
||||||
update_attrs(ui, clear_attrs);
|
update_attrs(ui, clear_attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute start/stop/step for the loop below, also use terminal scroll
|
|
||||||
// if possible
|
|
||||||
int start, stop, step;
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
start = top;
|
|
||||||
stop = bot - count + 1;
|
|
||||||
step = 1;
|
|
||||||
if (data->can_use_terminal_scroll) {
|
if (data->can_use_terminal_scroll) {
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
unibi_out(ui, unibi_delete_line);
|
unibi_out(ui, unibi_delete_line);
|
||||||
@ -484,9 +496,6 @@ static void tui_scroll(UI *ui, int count)
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
start = bot;
|
|
||||||
stop = top - count - 1;
|
|
||||||
step = -1;
|
|
||||||
if (data->can_use_terminal_scroll) {
|
if (data->can_use_terminal_scroll) {
|
||||||
if (count == -1) {
|
if (count == -1) {
|
||||||
unibi_out(ui, unibi_insert_line);
|
unibi_out(ui, unibi_insert_line);
|
||||||
@ -502,52 +511,30 @@ static void tui_scroll(UI *ui, int count)
|
|||||||
data->params[0].i = 0;
|
data->params[0].i = 0;
|
||||||
data->params[1].i = ui->height - 1;
|
data->params[1].i = ui->height - 1;
|
||||||
unibi_out(ui, unibi_change_scroll_region);
|
unibi_out(ui, unibi_change_scroll_region);
|
||||||
unibi_goto(ui, data->row, data->col);
|
unibi_goto(ui, grid->row, grid->col);
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
if (grid->bg != -1) {
|
||||||
// Scroll internal screen
|
// Update the cleared area of the terminal if its builtin scrolling
|
||||||
for (i = start; i != stop; i += step) {
|
// facility was used and the background color is not the default. This is
|
||||||
Cell *target_row = data->screen[i] + left;
|
// required because scrolling may leave wrong background in the cleared
|
||||||
Cell *source_row = data->screen[i + count] + left;
|
// area.
|
||||||
memcpy(target_row, source_row, sizeof(Cell) * (size_t)(right - left + 1));
|
clear_region(ui, clear_top, clear_bot, grid->left, grid->right);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear emptied region, updating the terminal if its builtin scrolling
|
|
||||||
// facility was used. This is done when the background color is not the
|
|
||||||
// default, since scrolling may leave wrong background in the cleared area.
|
|
||||||
bool update_clear = data->bg != -1 && data->can_use_terminal_scroll;
|
|
||||||
if (count > 0) {
|
|
||||||
clear_region(ui, stop, stop + count - 1, left, right, update_clear);
|
|
||||||
} else {
|
} else {
|
||||||
clear_region(ui, stop + count + 1, stop, left, right, update_clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data->can_use_terminal_scroll) {
|
|
||||||
// Mark the entire scroll region as invalid for redrawing later
|
// Mark the entire scroll region as invalid for redrawing later
|
||||||
invalidate(ui, data->scroll_region.top, data->scroll_region.bot,
|
invalidate(ui, grid->top, grid->bot, grid->left, grid->right);
|
||||||
data->scroll_region.left, data->scroll_region.right);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_highlight_set(UI *ui, HlAttrs attrs)
|
static void tui_highlight_set(UI *ui, HlAttrs attrs)
|
||||||
{
|
{
|
||||||
((TUIData *)ui->data)->attrs = attrs;
|
((TUIData *)ui->data)->grid.attrs = attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_put(UI *ui, uint8_t *text, size_t size)
|
static void tui_put(UI *ui, uint8_t *text, size_t size)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
Cell *cell = data->screen[data->row] + data->col;
|
print_cell(ui, ugrid_put(&data->grid, text, size));
|
||||||
cell->data[size] = 0;
|
|
||||||
cell->attrs = data->attrs;
|
|
||||||
|
|
||||||
if (text) {
|
|
||||||
memcpy(cell->data, text, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
print_cell(ui, cell);
|
|
||||||
data->col += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_bell(UI *ui)
|
static void tui_bell(UI *ui)
|
||||||
@ -562,40 +549,64 @@ static void tui_visual_bell(UI *ui)
|
|||||||
|
|
||||||
static void tui_update_fg(UI *ui, int fg)
|
static void tui_update_fg(UI *ui, int fg)
|
||||||
{
|
{
|
||||||
((TUIData *)ui->data)->fg = fg;
|
((TUIData *)ui->data)->grid.fg = fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_update_bg(UI *ui, int bg)
|
static void tui_update_bg(UI *ui, int bg)
|
||||||
{
|
{
|
||||||
((TUIData *)ui->data)->bg = bg;
|
((TUIData *)ui->data)->grid.bg = bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_flush(UI *ui)
|
static void tui_flush(UI *ui)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
|
UGrid *grid = &data->grid;
|
||||||
|
|
||||||
while (kv_size(data->invalid_regions)) {
|
while (kv_size(data->invalid_regions)) {
|
||||||
Rect r = kv_pop(data->invalid_regions);
|
Rect r = kv_pop(data->invalid_regions);
|
||||||
FOREACH_CELL(ui, r.top, r.bot, r.left, r.right, true, {
|
int currow = -1;
|
||||||
|
UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, {
|
||||||
|
if (currow != row) {
|
||||||
|
unibi_goto(ui, row, col);
|
||||||
|
currow = row;
|
||||||
|
}
|
||||||
print_cell(ui, cell);
|
print_cell(ui, cell);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unibi_goto(ui, data->row, data->col);
|
unibi_goto(ui, grid->row, grid->col);
|
||||||
|
|
||||||
flush_buf(ui);
|
flush_buf(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void suspend_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = argv[0];
|
||||||
|
TUIData *data = ui->data;
|
||||||
|
bool enable_mouse = data->mouse_enabled;
|
||||||
|
tui_terminal_stop(ui);
|
||||||
|
data->cont_received = false;
|
||||||
|
kill(0, SIGTSTP);
|
||||||
|
while (!data->cont_received) {
|
||||||
|
// poll the event loop until SIGCONT is received
|
||||||
|
loop_poll_events(data->loop, -1);
|
||||||
|
}
|
||||||
|
tui_terminal_start(ui);
|
||||||
|
if (enable_mouse) {
|
||||||
|
tui_mouse_on(ui);
|
||||||
|
}
|
||||||
|
// resume the main thread
|
||||||
|
CONTINUE(data->bridge);
|
||||||
|
}
|
||||||
|
|
||||||
static void tui_suspend(UI *ui)
|
static void tui_suspend(UI *ui)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
bool enable_mouse = data->mouse_enabled;
|
// kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
|
||||||
tui_stop(ui);
|
// before continuing. This is done in another callback to avoid
|
||||||
kill(0, SIGTSTP);
|
// loop_poll_events recursion
|
||||||
ui = tui_start();
|
queue_put_event(data->loop->fast_events,
|
||||||
if (enable_mouse) {
|
event_create(1, suspend_event, 1, ui));
|
||||||
tui_mouse_on(ui);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_set_title(UI *ui, char *title)
|
static void tui_set_title(UI *ui, char *title)
|
||||||
@ -617,7 +628,7 @@ static void tui_set_icon(UI *ui, char *icon)
|
|||||||
static void tui_set_encoding(UI *ui, char* enc)
|
static void tui_set_encoding(UI *ui, char* enc)
|
||||||
{
|
{
|
||||||
TUIData *data = ui->data;
|
TUIData *data = ui->data;
|
||||||
term_input_set_encoding(data->input, enc);
|
term_input_set_encoding(&data->input, enc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void invalidate(UI *ui, int top, int bot, int left, int right)
|
static void invalidate(UI *ui, int top, int bot, int left, int right)
|
||||||
@ -698,8 +709,8 @@ end:
|
|||||||
height = DFLT_ROWS;
|
height = DFLT_ROWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->width = width;
|
data->bridge->bridge.width = ui->width = width;
|
||||||
ui->height = height;
|
data->bridge->bridge.height = ui->height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unibi_goto(UI *ui, int row, int col)
|
static void unibi_goto(UI *ui, int row, int col)
|
||||||
@ -882,7 +893,7 @@ static void flush_buf(UI *ui)
|
|||||||
buf.base = data->buf;
|
buf.base = data->buf;
|
||||||
buf.len = data->bufpos;
|
buf.len = data->bufpos;
|
||||||
uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL);
|
uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL);
|
||||||
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
uv_run(&data->write_loop, UV_RUN_DEFAULT);
|
||||||
data->bufpos = 0;
|
data->bufpos = 0;
|
||||||
|
|
||||||
if (!data->busy) {
|
if (!data->busy) {
|
||||||
@ -891,13 +902,3 @@ static void flush_buf(UI *ui)
|
|||||||
unibi_out(ui, unibi_cursor_invisible);
|
unibi_out(ui, unibi_cursor_invisible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void destroy_screen(TUIData *data)
|
|
||||||
{
|
|
||||||
if (data->screen) {
|
|
||||||
for (int i = 0; i < data->old_height; i++) {
|
|
||||||
xfree(data->screen[i]);
|
|
||||||
}
|
|
||||||
xfree(data->screen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
137
src/nvim/ugrid.c
Normal file
137
src/nvim/ugrid.c
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/ui.h"
|
||||||
|
#include "nvim/ugrid.h"
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "ugrid.c.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ugrid_init(UGrid *grid)
|
||||||
|
{
|
||||||
|
grid->attrs = EMPTY_ATTRS;
|
||||||
|
grid->fg = grid->bg = -1;
|
||||||
|
grid->cells = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_free(UGrid *grid)
|
||||||
|
{
|
||||||
|
destroy_cells(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_resize(UGrid *grid, int width, int height)
|
||||||
|
{
|
||||||
|
destroy_cells(grid);
|
||||||
|
grid->cells = xmalloc((size_t)height * sizeof(UCell *));
|
||||||
|
for (int i = 0; i < height; i++) {
|
||||||
|
grid->cells[i] = xcalloc((size_t)width, sizeof(UCell));
|
||||||
|
}
|
||||||
|
|
||||||
|
grid->top = 0;
|
||||||
|
grid->bot = height - 1;
|
||||||
|
grid->left = 0;
|
||||||
|
grid->right = width - 1;
|
||||||
|
grid->row = grid->col = 0;
|
||||||
|
grid->width = width;
|
||||||
|
grid->height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_clear(UGrid *grid)
|
||||||
|
{
|
||||||
|
clear_region(grid, grid->top, grid->bot, grid->left, grid->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_eol_clear(UGrid *grid)
|
||||||
|
{
|
||||||
|
clear_region(grid, grid->row, grid->row, grid->col, grid->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_goto(UGrid *grid, int row, int col)
|
||||||
|
{
|
||||||
|
grid->row = row;
|
||||||
|
grid->col = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_set_scroll_region(UGrid *grid, int top, int bot, int left, int right)
|
||||||
|
{
|
||||||
|
grid->top = top;
|
||||||
|
grid->bot = bot;
|
||||||
|
grid->left = left;
|
||||||
|
grid->right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
|
||||||
|
{
|
||||||
|
// Compute start/stop/step for the loop below
|
||||||
|
int start, stop, step;
|
||||||
|
if (count > 0) {
|
||||||
|
start = grid->top;
|
||||||
|
stop = grid->bot - count + 1;
|
||||||
|
step = 1;
|
||||||
|
} else {
|
||||||
|
start = grid->bot;
|
||||||
|
stop = grid->top - count - 1;
|
||||||
|
step = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// Copy cell data
|
||||||
|
for (i = start; i != stop; i += step) {
|
||||||
|
UCell *target_row = grid->cells[i] + grid->left;
|
||||||
|
UCell *source_row = grid->cells[i + count] + grid->left;
|
||||||
|
memcpy(target_row, source_row,
|
||||||
|
sizeof(UCell) * (size_t)(grid->right - grid->left + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear cells in the emptied region,
|
||||||
|
if (count > 0) {
|
||||||
|
*clear_top = stop;
|
||||||
|
*clear_bot = stop + count - 1;
|
||||||
|
} else {
|
||||||
|
*clear_bot = stop;
|
||||||
|
*clear_top = stop + count + 1;
|
||||||
|
}
|
||||||
|
clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
UCell *cell = grid->cells[grid->row] + grid->col;
|
||||||
|
cell->data[size] = 0;
|
||||||
|
cell->attrs = grid->attrs;
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
memcpy(cell->data, text, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
grid->col += 1;
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_region(UGrid *grid, int top, int bot, int left, int right)
|
||||||
|
{
|
||||||
|
HlAttrs clear_attrs = EMPTY_ATTRS;
|
||||||
|
clear_attrs.foreground = grid->fg;
|
||||||
|
clear_attrs.background = grid->bg;
|
||||||
|
UGRID_FOREACH_CELL(grid, top, bot, left, right, {
|
||||||
|
cell->data[0] = ' ';
|
||||||
|
cell->data[1] = 0;
|
||||||
|
cell->attrs = clear_attrs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_cells(UGrid *grid)
|
||||||
|
{
|
||||||
|
if (grid->cells) {
|
||||||
|
for (int i = 0; i < grid->height; i++) {
|
||||||
|
xfree(grid->cells[i]);
|
||||||
|
}
|
||||||
|
xfree(grid->cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
40
src/nvim/ugrid.h
Normal file
40
src/nvim/ugrid.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#ifndef NVIM_UGRID_H
|
||||||
|
#define NVIM_UGRID_H
|
||||||
|
|
||||||
|
#include "nvim/ui.h"
|
||||||
|
|
||||||
|
typedef struct ucell UCell;
|
||||||
|
typedef struct ugrid UGrid;
|
||||||
|
|
||||||
|
struct ucell {
|
||||||
|
char data[7];
|
||||||
|
HlAttrs attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ugrid {
|
||||||
|
int top, bot, left, right;
|
||||||
|
int row, col;
|
||||||
|
int bg, fg;
|
||||||
|
int width, height;
|
||||||
|
HlAttrs attrs;
|
||||||
|
UCell **cells;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EMPTY_ATTRS ((HlAttrs){false, false, false, false, false, -1, -1})
|
||||||
|
|
||||||
|
#define UGRID_FOREACH_CELL(grid, top, bot, left, right, code) \
|
||||||
|
do { \
|
||||||
|
for (int row = top; row <= bot; ++row) { \
|
||||||
|
UCell *row_cells = (grid)->cells[row]; \
|
||||||
|
for (int col = left; col <= right; ++col) { \
|
||||||
|
UCell *cell = row_cells + col; \
|
||||||
|
(void)(cell); \
|
||||||
|
code; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "ugrid.h.generated.h"
|
||||||
|
#endif
|
||||||
|
#endif // NVIM_UGRID_H
|
347
src/nvim/ui_bridge.c
Normal file
347
src/nvim/ui_bridge.c
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
// FIXME(tarruda): This module is very repetitive. It might be a good idea to
|
||||||
|
// automatically generate it with a lua script during build
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/ui.h"
|
||||||
|
#include "nvim/memory.h"
|
||||||
|
#include "nvim/ui_bridge.h"
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "ui_bridge.c.generated.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define UI(b) (((UIBridgeData *)b)->ui)
|
||||||
|
|
||||||
|
// Call a function in the UI thread
|
||||||
|
#define UI_CALL(ui, name, argc, ...) \
|
||||||
|
((UIBridgeData *)ui)->scheduler( \
|
||||||
|
event_create(1, ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui))
|
||||||
|
|
||||||
|
#define INT2PTR(i) ((void *)(uintptr_t)i)
|
||||||
|
#define PTR2INT(p) ((int)(uintptr_t)p)
|
||||||
|
|
||||||
|
UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
|
||||||
|
{
|
||||||
|
UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData));
|
||||||
|
rv->ui = ui;
|
||||||
|
rv->bridge.rgb = ui->rgb;
|
||||||
|
rv->bridge.stop = ui_bridge_stop;
|
||||||
|
rv->bridge.resize = ui_bridge_resize;
|
||||||
|
rv->bridge.clear = ui_bridge_clear;
|
||||||
|
rv->bridge.eol_clear = ui_bridge_eol_clear;
|
||||||
|
rv->bridge.cursor_goto = ui_bridge_cursor_goto;
|
||||||
|
rv->bridge.update_menu = ui_bridge_update_menu;
|
||||||
|
rv->bridge.busy_start = ui_bridge_busy_start;
|
||||||
|
rv->bridge.busy_stop = ui_bridge_busy_stop;
|
||||||
|
rv->bridge.mouse_on = ui_bridge_mouse_on;
|
||||||
|
rv->bridge.mouse_off = ui_bridge_mouse_off;
|
||||||
|
rv->bridge.mode_change = ui_bridge_mode_change;
|
||||||
|
rv->bridge.set_scroll_region = ui_bridge_set_scroll_region;
|
||||||
|
rv->bridge.scroll = ui_bridge_scroll;
|
||||||
|
rv->bridge.highlight_set = ui_bridge_highlight_set;
|
||||||
|
rv->bridge.put = ui_bridge_put;
|
||||||
|
rv->bridge.bell = ui_bridge_bell;
|
||||||
|
rv->bridge.visual_bell = ui_bridge_visual_bell;
|
||||||
|
rv->bridge.update_fg = ui_bridge_update_fg;
|
||||||
|
rv->bridge.update_bg = ui_bridge_update_bg;
|
||||||
|
rv->bridge.flush = ui_bridge_flush;
|
||||||
|
rv->bridge.suspend = ui_bridge_suspend;
|
||||||
|
rv->bridge.set_title = ui_bridge_set_title;
|
||||||
|
rv->bridge.set_icon = ui_bridge_set_icon;
|
||||||
|
rv->bridge.set_encoding = ui_bridge_set_encoding;
|
||||||
|
rv->scheduler = scheduler;
|
||||||
|
|
||||||
|
rv->ui_main = ui_main;
|
||||||
|
uv_mutex_init(&rv->mutex);
|
||||||
|
uv_cond_init(&rv->cond);
|
||||||
|
uv_mutex_lock(&rv->mutex);
|
||||||
|
rv->ready = false;
|
||||||
|
|
||||||
|
if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!rv->ready) {
|
||||||
|
uv_cond_wait(&rv->cond, &rv->mutex);
|
||||||
|
}
|
||||||
|
uv_mutex_unlock(&rv->mutex);
|
||||||
|
|
||||||
|
ui_attach(&rv->bridge);
|
||||||
|
return &rv->bridge;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_thread_run(void *data)
|
||||||
|
{
|
||||||
|
UIBridgeData *bridge = data;
|
||||||
|
bridge->ui_main(bridge, bridge->ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_stop(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, stop, 1, b);
|
||||||
|
UIBridgeData *bridge = (UIBridgeData *)b;
|
||||||
|
uv_thread_join(&bridge->ui_thread);
|
||||||
|
uv_mutex_destroy(&bridge->mutex);
|
||||||
|
uv_cond_destroy(&bridge->cond);
|
||||||
|
ui_detach(b);
|
||||||
|
xfree(b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_stop_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->stop(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_resize(UI *b, int width, int height)
|
||||||
|
{
|
||||||
|
UI_CALL(b, resize, 3, b, INT2PTR(width), INT2PTR(height));
|
||||||
|
}
|
||||||
|
static void ui_bridge_resize_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->resize(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_clear(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, clear, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_clear_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->clear(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_eol_clear(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, eol_clear, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_eol_clear_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->eol_clear(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_cursor_goto(UI *b, int row, int col)
|
||||||
|
{
|
||||||
|
UI_CALL(b, cursor_goto, 3, b, INT2PTR(row), INT2PTR(col));
|
||||||
|
}
|
||||||
|
static void ui_bridge_cursor_goto_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_update_menu(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, update_menu, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_update_menu_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->update_menu(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_busy_start(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, busy_start, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_busy_start_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->busy_start(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_busy_stop(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, busy_stop, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_busy_stop_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->busy_stop(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_mouse_on(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, mouse_on, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_mouse_on_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->mouse_on(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_mouse_off(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, mouse_off, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_mouse_off_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->mouse_off(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_mode_change(UI *b, int mode)
|
||||||
|
{
|
||||||
|
UI_CALL(b, mode_change, 2, b, INT2PTR(mode));
|
||||||
|
}
|
||||||
|
static void ui_bridge_mode_change_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->mode_change(ui, PTR2INT(argv[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_set_scroll_region(UI *b, int top, int bot, int left,
|
||||||
|
int right)
|
||||||
|
{
|
||||||
|
UI_CALL(b, set_scroll_region, 5, b, INT2PTR(top), INT2PTR(bot),
|
||||||
|
INT2PTR(left), INT2PTR(right));
|
||||||
|
}
|
||||||
|
static void ui_bridge_set_scroll_region_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->set_scroll_region(ui, PTR2INT(argv[1]), PTR2INT(argv[2]),
|
||||||
|
PTR2INT(argv[3]), PTR2INT(argv[4]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_scroll(UI *b, int count)
|
||||||
|
{
|
||||||
|
UI_CALL(b, scroll, 2, b, INT2PTR(count));
|
||||||
|
}
|
||||||
|
static void ui_bridge_scroll_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->scroll(ui, PTR2INT(argv[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_highlight_set(UI *b, HlAttrs attrs)
|
||||||
|
{
|
||||||
|
HlAttrs *a = xmalloc(sizeof(HlAttrs));
|
||||||
|
*a = attrs;
|
||||||
|
UI_CALL(b, highlight_set, 2, b, a);
|
||||||
|
}
|
||||||
|
static void ui_bridge_highlight_set_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->highlight_set(ui, *((HlAttrs *)argv[1]));
|
||||||
|
xfree(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_put(UI *b, uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
uint8_t *t = xmalloc(8);
|
||||||
|
memcpy(t, text, size);
|
||||||
|
UI_CALL(b, put, 3, b, t, INT2PTR(size));
|
||||||
|
}
|
||||||
|
static void ui_bridge_put_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->put(ui, (uint8_t *)argv[1], (size_t)(uintptr_t)argv[2]);
|
||||||
|
xfree(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_bell(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, bell, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_bell_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->bell(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_visual_bell(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, visual_bell, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_visual_bell_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->visual_bell(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_update_fg(UI *b, int fg)
|
||||||
|
{
|
||||||
|
UI_CALL(b, update_fg, 2, b, INT2PTR(fg));
|
||||||
|
}
|
||||||
|
static void ui_bridge_update_fg_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->update_fg(ui, PTR2INT(argv[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_update_bg(UI *b, int bg)
|
||||||
|
{
|
||||||
|
UI_CALL(b, update_bg, 2, b, INT2PTR(bg));
|
||||||
|
}
|
||||||
|
static void ui_bridge_update_bg_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->update_bg(ui, PTR2INT(argv[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_flush(UI *b)
|
||||||
|
{
|
||||||
|
UI_CALL(b, flush, 1, b);
|
||||||
|
}
|
||||||
|
static void ui_bridge_flush_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->flush(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_suspend(UI *b)
|
||||||
|
{
|
||||||
|
UIBridgeData *data = (UIBridgeData *)b;
|
||||||
|
uv_mutex_lock(&data->mutex);
|
||||||
|
UI_CALL(b, suspend, 1, b);
|
||||||
|
data->ready = false;
|
||||||
|
// suspend the main thread until CONTINUE is called by the UI thread
|
||||||
|
while (!data->ready) {
|
||||||
|
uv_cond_wait(&data->cond, &data->mutex);
|
||||||
|
}
|
||||||
|
uv_mutex_unlock(&data->mutex);
|
||||||
|
}
|
||||||
|
static void ui_bridge_suspend_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->suspend(ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_set_title(UI *b, char *title)
|
||||||
|
{
|
||||||
|
UI_CALL(b, set_title, 2, b, title ? xstrdup(title) : NULL);
|
||||||
|
}
|
||||||
|
static void ui_bridge_set_title_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->set_title(ui, argv[1]);
|
||||||
|
xfree(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_set_icon(UI *b, char *icon)
|
||||||
|
{
|
||||||
|
UI_CALL(b, set_icon, 2, b, icon ? xstrdup(icon) : NULL);
|
||||||
|
}
|
||||||
|
static void ui_bridge_set_icon_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->set_icon(ui, argv[1]);
|
||||||
|
xfree(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ui_bridge_set_encoding(UI *b, char* enc)
|
||||||
|
{
|
||||||
|
UI_CALL(b, set_encoding, 2, b, xstrdup(enc));
|
||||||
|
}
|
||||||
|
static void ui_bridge_set_encoding_event(void **argv)
|
||||||
|
{
|
||||||
|
UI *ui = UI(argv[0]);
|
||||||
|
ui->set_encoding(ui, argv[1]);
|
||||||
|
xfree(argv[1]);
|
||||||
|
}
|
40
src/nvim/ui_bridge.h
Normal file
40
src/nvim/ui_bridge.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Bridge used for communication between a builtin UI thread and nvim core
|
||||||
|
#ifndef NVIM_UI_BRIDGE_H
|
||||||
|
#define NVIM_UI_BRIDGE_H
|
||||||
|
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
#include "nvim/ui.h"
|
||||||
|
#include "nvim/event/defs.h"
|
||||||
|
|
||||||
|
typedef struct ui_bridge_data UIBridgeData;
|
||||||
|
typedef void(*ui_main_fn)(UIBridgeData *bridge, UI *ui);
|
||||||
|
struct ui_bridge_data {
|
||||||
|
UI bridge; // actual UI passed to ui_attach
|
||||||
|
UI *ui; // UI pointer that will have it's callback called in
|
||||||
|
// another thread
|
||||||
|
event_scheduler scheduler;
|
||||||
|
uv_thread_t ui_thread;
|
||||||
|
ui_main_fn ui_main;
|
||||||
|
uv_mutex_t mutex;
|
||||||
|
uv_cond_t cond;
|
||||||
|
// When the UI thread is called, the main thread will suspend until
|
||||||
|
// the call returns. This flag is used as a condition for the main
|
||||||
|
// thread to continue.
|
||||||
|
bool ready;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CONTINUE(b) \
|
||||||
|
do { \
|
||||||
|
UIBridgeData *d = (UIBridgeData *)b; \
|
||||||
|
uv_mutex_lock(&d->mutex); \
|
||||||
|
d->ready = true; \
|
||||||
|
uv_cond_signal(&d->cond); \
|
||||||
|
uv_mutex_unlock(&d->mutex); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "ui_bridge.h.generated.h"
|
||||||
|
#endif
|
||||||
|
#endif // NVIM_UI_BRIDGE_H
|
Loading…
Reference in New Issue
Block a user