Files
neovim/src/nvim/api/helpers.c
Eliseo Martínez e731a5edf8 Introduce nvim namespace: Fix relative includes.
Problem:  Some newly introduced files used includes relative to the
          current file, both of the form `include "../XXX.h"` and
          `include "XXX.h"`.
          Preferred form is relative to include root (src/ in our case).
Solution: Change includes to preferred form.
Note:     This is also done to ease next commit (prepend 'nvim/ to all
          project-local includes).
2014-05-15 20:46:01 +02:00

579 lines
14 KiB
C

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "api/helpers.h"
#include "api/defs.h"
#include "vim.h"
#include "buffer.h"
#include "window.h"
#include "memory.h"
#include "eval.h"
#include "option.h"
#include "option_defs.h"
#include "lib/khash.h"
#if defined(ARCH_64)
typedef uint64_t ptr_int_t;
KHASH_SET_INIT_INT64(Lookup)
#elif defined(ARCH_32)
typedef uint32_t ptr_int_t;
KHASH_SET_INIT_INT(Lookup)
#endif
/// Recursion helper for the `vim_to_object`. This uses a pointer table
/// to avoid infinite recursion due to cyclic references
///
/// @param obj The source object
/// @param lookup Lookup table containing pointers to all processed objects
/// @return The converted value
static Object vim_to_object_rec(typval_T *obj, khash_t(Lookup) *lookup);
static bool object_to_vim(Object obj, typval_T *tv, Error *err);
static void set_option_value_for(char *key,
int numval,
char *stringval,
int opt_flags,
int opt_type,
void *from,
Error *err);
static void set_option_value_err(char *key,
int numval,
char *stringval,
int opt_flags,
Error *err);
void try_start()
{
++trylevel;
}
bool try_end(Error *err)
{
--trylevel;
// Without this it stops processing all subsequent VimL commands and
// generates strange error messages if I e.g. try calling Test() in a
// cycle
did_emsg = false;
if (got_int) {
if (did_throw) {
// If we got an interrupt, discard the current exception
discard_current_exception();
}
set_api_error("Keyboard interrupt", err);
got_int = false;
} else if (msg_list != NULL && *msg_list != NULL) {
int should_free;
char *msg = (char *)get_exception_string(*msg_list,
ET_ERROR,
NULL,
&should_free);
strncpy(err->msg, msg, sizeof(err->msg));
err->set = true;
free_global_msglist();
if (should_free) {
free(msg);
}
} else if (did_throw) {
set_api_error((char *)current_exception->value, err);
}
return err->set;
}
Object dict_get_value(dict_T *dict, String key, Error *err)
{
Object rv;
hashitem_T *hi;
dictitem_T *di;
char *k = xstrndup(key.data, key.size);
hi = hash_find(&dict->dv_hashtab, (uint8_t *)k);
free(k);
if (HASHITEM_EMPTY(hi)) {
set_api_error("Key not found", err);
return rv;
}
di = dict_lookup(hi);
rv = vim_to_object(&di->di_tv);
return rv;
}
Object dict_set_value(dict_T *dict, String key, Object value, Error *err)
{
Object rv = {.type = kObjectTypeNil};
if (dict->dv_lock) {
set_api_error("Dictionary is locked", err);
return rv;
}
if (key.size == 0) {
set_api_error("Empty dictionary keys aren't allowed", err);
return rv;
}
dictitem_T *di = dict_find(dict, (uint8_t *)key.data, key.size);
if (value.type == kObjectTypeNil) {
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
set_api_error("Key doesn't exist", err);
} else {
// Return the old value
rv = vim_to_object(&di->di_tv);
// Delete the entry
hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key);
hash_remove(&dict->dv_hashtab, hi);
dictitem_free(di);
}
} else {
// Update the key
typval_T tv;
// Convert the object to a vimscript type in the temporary variable
if (!object_to_vim(value, &tv, err)) {
return rv;
}
if (di == NULL) {
// Need to create an entry
char *k = xstrndup(key.data, key.size);
di = dictitem_alloc((uint8_t *)k);
free(k);
dict_add(dict, di);
} else {
// Return the old value
clear_tv(&di->di_tv);
}
// Update the value
copy_tv(&tv, &di->di_tv);
// Clear the temporary variable
clear_tv(&tv);
}
return rv;
}
Object get_option_from(void *from, int type, String name, Error *err)
{
Object rv = {.type = kObjectTypeNil};
if (name.size == 0) {
set_api_error("Empty option name", err);
return rv;
}
// Return values
int64_t numval;
char *stringval = NULL;
// copy the option name into 0-delimited string
char *key = xstrndup(name.data, name.size);
int flags = get_option_value_strict(key, &numval, &stringval, type, from);
free(key);
if (!flags) {
set_api_error("invalid option name", err);
return rv;
}
if (flags & SOPT_BOOL) {
rv.type = kObjectTypeBool;
rv.data.boolean = numval ? true : false;
} else if (flags & SOPT_NUM) {
rv.type = kObjectTypeInt;
rv.data.integer = numval;
} else if (flags & SOPT_STRING) {
if (stringval) {
rv.type = kObjectTypeString;
rv.data.string.data = stringval;
rv.data.string.size = strlen(stringval);
} else {
set_api_error(N_("Unable to get option value"), err);
}
} else {
set_api_error(N_("internal error: unknown option type"), err);
}
return rv;
}
void set_option_to(void *to, int type, String name, Object value, Error *err)
{
if (name.size == 0) {
set_api_error("Empty option name", err);
return;
}
char *key = xstrndup(name.data, name.size);
int flags = get_option_value_strict(key, NULL, NULL, type, to);
if (flags == 0) {
set_api_error("invalid option name", err);
goto cleanup;
}
if (value.type == kObjectTypeNil) {
if (type == SREQ_GLOBAL) {
set_api_error("unable to unset option", err);
goto cleanup;
} else if (!(flags & SOPT_GLOBAL)) {
set_api_error("cannot unset option that doesn't have a global value",
err);
goto cleanup;
} else {
unset_global_local_option(key, to);
goto cleanup;
}
}
int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL);
if (flags & SOPT_BOOL) {
if (value.type != kObjectTypeBool) {
set_api_error("option requires a boolean value", err);
goto cleanup;
}
bool val = value.data.boolean;
set_option_value_for(key, val, NULL, opt_flags, type, to, err);
} else if (flags & SOPT_NUM) {
if (value.type != kObjectTypeInt) {
set_api_error("option requires an integer value", err);
goto cleanup;
}
int val = value.data.integer;
set_option_value_for(key, val, NULL, opt_flags, type, to, err);
} else {
if (value.type != kObjectTypeString) {
set_api_error("option requires a string value", err);
goto cleanup;
}
char *val = xstrndup(value.data.string.data, value.data.string.size);
set_option_value_for(key, 0, val, opt_flags, type, to, err);
}
cleanup:
free(key);
}
Object vim_to_object(typval_T *obj)
{
Object rv;
// We use a lookup table to break out of cyclic references
khash_t(Lookup) *lookup = kh_init(Lookup);
rv = vim_to_object_rec(obj, lookup);
// Free the table
kh_destroy(Lookup, lookup);
return rv;
}
buf_T *find_buffer(Buffer buffer, Error *err)
{
buf_T *buf = buflist_findnr(buffer);
if (buf == NULL) {
set_api_error("Invalid buffer id", err);
}
return buf;
}
win_T * find_window(Window window, Error *err)
{
tabpage_T *tp;
win_T *wp;
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (!--window) {
return wp;
}
}
set_api_error("Invalid window id", err);
return NULL;
}
tabpage_T * find_tab(Tabpage tabpage, Error *err)
{
tabpage_T *rv = find_tabpage(tabpage);
if (!rv) {
set_api_error("Invalid tabpage id", err);
}
return rv;
}
static bool object_to_vim(Object obj, typval_T *tv, Error *err)
{
tv->v_type = VAR_UNKNOWN;
tv->v_lock = 0;
switch (obj.type) {
case kObjectTypeNil:
tv->v_type = VAR_NUMBER;
tv->vval.v_number = 0;
break;
case kObjectTypeBool:
tv->v_type = VAR_NUMBER;
tv->vval.v_number = obj.data.boolean;
break;
case kObjectTypeInt:
tv->v_type = VAR_NUMBER;
tv->vval.v_number = obj.data.integer;
break;
case kObjectTypeFloat:
tv->v_type = VAR_FLOAT;
tv->vval.v_float = obj.data.floating_point;
break;
case kObjectTypeString:
tv->v_type = VAR_STRING;
tv->vval.v_string = (uint8_t *)xstrndup(obj.data.string.data,
obj.data.string.size);
break;
case kObjectTypeArray:
tv->v_type = VAR_LIST;
tv->vval.v_list = list_alloc();
for (uint32_t i = 0; i < obj.data.array.size; i++) {
Object item = obj.data.array.items[i];
listitem_T *li = listitem_alloc();
if (!object_to_vim(item, &li->li_tv, err)) {
// cleanup
listitem_free(li);
list_free(tv->vval.v_list, true);
return false;
}
list_append(tv->vval.v_list, li);
}
tv->vval.v_list->lv_refcount++;
break;
case kObjectTypeDictionary:
tv->v_type = VAR_DICT;
tv->vval.v_dict = dict_alloc();
for (uint32_t i = 0; i < obj.data.dictionary.size; i++) {
KeyValuePair item = obj.data.dictionary.items[i];
String key = item.key;
if (key.size == 0) {
set_api_error("Empty dictionary keys aren't allowed", err);
// cleanup
dict_free(tv->vval.v_dict, true);
return false;
}
char *k = xstrndup(key.data, key.size);
dictitem_T *di = dictitem_alloc((uint8_t *)k);
free(k);
if (!object_to_vim(item.value, &di->di_tv, err)) {
// cleanup
dictitem_free(di);
dict_free(tv->vval.v_dict, true);
return false;
}
dict_add(tv->vval.v_dict, di);
}
tv->vval.v_dict->dv_refcount++;
break;
}
return true;
}
static Object vim_to_object_rec(typval_T *obj, khash_t(Lookup) *lookup)
{
Object rv = {.type = kObjectTypeNil};
if (obj->v_type == VAR_LIST || obj->v_type == VAR_DICT) {
int ret;
// Container object, add it to the lookup table
kh_put(Lookup, lookup, (ptr_int_t)obj, &ret);
if (!ret) {
// It's already present, meaning we alredy processed it so just return
// nil instead.
return rv;
}
}
switch (obj->v_type) {
case VAR_STRING:
if (obj->vval.v_string != NULL) {
rv.type = kObjectTypeString;
rv.data.string.data = xstrdup((char *)obj->vval.v_string);
rv.data.string.size = strlen(rv.data.string.data);
}
break;
case VAR_NUMBER:
rv.type = kObjectTypeInt;
rv.data.integer = obj->vval.v_number;
break;
case VAR_FLOAT:
rv.type = kObjectTypeFloat;
rv.data.floating_point = obj->vval.v_float;
break;
case VAR_LIST:
{
list_T *list = obj->vval.v_list;
listitem_T *item;
if (list != NULL) {
rv.type = kObjectTypeArray;
rv.data.array.size = list->lv_len;
rv.data.array.items = xmalloc(list->lv_len * sizeof(Object));
uint32_t i = 0;
for (item = list->lv_first; item != NULL; item = item->li_next) {
rv.data.array.items[i] = vim_to_object_rec(&item->li_tv, lookup);
i++;
}
}
}
break;
case VAR_DICT:
{
dict_T *dict = obj->vval.v_dict;
hashtab_T *ht;
uint64_t todo;
hashitem_T *hi;
dictitem_T *di;
if (dict != NULL) {
ht = &obj->vval.v_dict->dv_hashtab;
todo = ht->ht_used;
rv.type = kObjectTypeDictionary;
// Count items
rv.data.dictionary.size = 0;
for (hi = ht->ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
rv.data.dictionary.size++;
}
}
rv.data.dictionary.items =
xmalloc(rv.data.dictionary.size * sizeof(KeyValuePair));
todo = ht->ht_used;
uint32_t i = 0;
// Convert all
for (hi = ht->ht_array; todo > 0; ++hi) {
if (!HASHITEM_EMPTY(hi)) {
di = dict_lookup(hi);
// Convert key
rv.data.dictionary.items[i].key.data =
xstrdup((char *)hi->hi_key);
rv.data.dictionary.items[i].key.size =
strlen((char *)hi->hi_key);
// Convert value
rv.data.dictionary.items[i].value =
vim_to_object_rec(&di->di_tv, lookup);
todo--;
i++;
}
}
}
}
break;
}
return rv;
}
static void set_option_value_for(char *key,
int numval,
char *stringval,
int opt_flags,
int opt_type,
void *from,
Error *err)
{
win_T *save_curwin = NULL;
tabpage_T *save_curtab = NULL;
buf_T *save_curbuf = NULL;
try_start();
switch (opt_type)
{
case SREQ_WIN:
if (switch_win(&save_curwin, &save_curtab, (win_T *)from,
win_find_tabpage((win_T *)from), false) == FAIL)
{
if (try_end(err)) {
return;
}
set_api_error("problem while switching windows", err);
return;
}
set_option_value_err(key, numval, stringval, opt_flags, err);
restore_win(save_curwin, save_curtab, true);
break;
case SREQ_BUF:
switch_buffer(&save_curbuf, (buf_T *)from);
set_option_value_err(key, numval, stringval, opt_flags, err);
restore_buffer(save_curbuf);
break;
case SREQ_GLOBAL:
set_option_value_err(key, numval, stringval, opt_flags, err);
break;
}
if (err->set) {
return;
}
try_end(err);
}
static void set_option_value_err(char *key,
int numval,
char *stringval,
int opt_flags,
Error *err)
{
char *errmsg;
if ((errmsg = (char *)set_option_value((uint8_t *)key,
numval,
(uint8_t *)stringval,
opt_flags)))
{
if (try_end(err)) {
return;
}
set_api_error(errmsg, err);
}
}