-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 832b075
Showing
12 changed files
with
1,531 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Files and directories created by pub | ||
.dart_tool/ | ||
.packages | ||
pubspec.lock | ||
|
||
# Conventional directory for build outputs | ||
build/ | ||
|
||
# Directory created by dartdoc | ||
doc/api/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 1.0.0 | ||
|
||
- Initial version, created by Stagehand |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Dart Nvim API | ||
Neovim API implementation for [Dart](dart.dev). Still a WIP, so | ||
any feedback, contributions, etc. are greatly appreciated. | ||
|
||
__NOTE__: Currently, this library | ||
only supports _asynchronous_ calls to Neovim. This is due to how | ||
Dart's [`Socket` class](https://api.dartlang.org/stable/2.5.2/dart-io/Socket-class.html) (used | ||
internally by the `Session` class here), is best used with `async/await`. However, | ||
I do plan to (attempt) creating a blocking implementation, so feel free to open an issue | ||
on the topic if you have any suggestsions or want to help. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Defines a default set of lint rules enforced for | ||
# projects at Google. For details and rationale, | ||
# see https://github.com/dart-lang/pedantic#enabled-lints. | ||
include: package:pedantic/analysis_options.yaml | ||
|
||
analyzer: | ||
exclude: | ||
- gen_bindings/neovim.dart |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
#!/usr/bin/env python | ||
""" | ||
Dart code generator, based on neovim-lib generator: | ||
https://github.com/daa84/neovim-lib/blob/master/bindings/generate_bindings.py | ||
""" | ||
|
||
import msgpack | ||
import sys | ||
import subprocess | ||
import os | ||
import re | ||
import datetime | ||
import string | ||
|
||
INPUT = 'bindings_gen' | ||
|
||
|
||
def make_camel_case(s): | ||
""" | ||
Formats the inputted snake_case string | ||
in camelCase. | ||
""" | ||
new_str = s.replace('_', ' ') | ||
new_str = new_str.split(' ') | ||
first_word = new_str[0] | ||
new_str = string.capwords(' '.join(new_str[1:])) | ||
new_str = first_word + new_str | ||
return new_str.replace(' ', '') | ||
|
||
|
||
def make_args_from_params(params): | ||
""" | ||
Returns a list which has `.codeData` added to the end | ||
of the names of each element of `params` with a type in | ||
`NeovimTypeVal.EXTTYPES` (`msgpack_dart` can't serialize those types, so it | ||
needs their internal representation, which is `codeData`). | ||
""" | ||
params_clone = params[:] | ||
for val in params_clone: | ||
if val.native_type_arg in NeovimTypeVal.EXTTYPES: | ||
val.name += '.codeData' | ||
|
||
return params_clone | ||
|
||
|
||
def decutf8(inp): | ||
""" | ||
Recursively decode bytes as utf8 into unicode | ||
""" | ||
if isinstance(inp, bytes): | ||
return inp.decode('utf8') | ||
elif isinstance(inp, list): | ||
return [decutf8(x) for x in inp] | ||
elif isinstance(inp, dict): | ||
return {decutf8(key): decutf8(val) for key, val in inp.items()} | ||
else: | ||
return inp | ||
|
||
|
||
def get_api_info(nvim): | ||
""" | ||
Call the neovim binary to get the api info | ||
""" | ||
args = [nvim, '--api-info'] | ||
info = subprocess.check_output(args) | ||
return decutf8(msgpack.unpackb(info)) | ||
|
||
|
||
def generate_file(name, outpath, **kw): | ||
from jinja2 import Environment, FileSystemLoader | ||
env = Environment(loader=FileSystemLoader( | ||
INPUT), trim_blocks=True) | ||
template = env.get_template(name) | ||
with open(os.path.join(outpath, name), 'w') as fp: | ||
fp.write(template.render(kw)) | ||
|
||
subprocess.call(["dartfmt", os.path.join(outpath, name), "-w"]) | ||
# os.remove(os.path.join(outpath, name + ".bk")) | ||
|
||
|
||
class UnsupportedType(Exception): | ||
pass | ||
|
||
|
||
class NeovimTypeVal: | ||
""" | ||
Representation for Neovim Parameter/Return | ||
""" | ||
# msgpack simple types types | ||
SIMPLETYPES_REF = { | ||
'Array': 'List<dynamic>', | ||
'ArrayOf(Integer, 2)': 'List<int>', | ||
'void': 'void', | ||
'Integer': 'int', | ||
'Boolean': 'bool', | ||
'String': 'String', | ||
'Object': 'dynamic', | ||
'Dictionary': 'Map<dynamic, dynamic>', | ||
} | ||
|
||
SIMPLETYPES_VAL = { | ||
'Array': 'List<dynamic>', | ||
'ArrayOf(Integer, 2)': 'List<int>', | ||
'void': 'void', | ||
'Integer': 'int', | ||
'Boolean': 'bool', | ||
'String': 'String', | ||
'Object': 'dynamic', | ||
'Dictionary': 'Map<dynamic, dynamic>', | ||
} | ||
# msgpack extension types | ||
EXTTYPES = { | ||
'Window': 'Window', | ||
'Buffer': 'Buffer', | ||
'Tabpage': 'Tabpage', | ||
} | ||
# Unbound Array types | ||
UNBOUND_ARRAY = re.compile('ArrayOf\(\s*(\w+)\s*\)') | ||
|
||
def __init__(self, typename, name=''): | ||
self.name = name | ||
self.neovim_type = typename | ||
self.ext = False | ||
self.native_type_arg = NeovimTypeVal.nativeTypeRef(typename) | ||
self.native_type_ret = NeovimTypeVal.nativeTypeVal(typename) | ||
|
||
if typename in self.EXTTYPES: | ||
self.ext = True | ||
|
||
def __getitem__(self, key): | ||
if key == "native_type_arg": | ||
return self.native_type_arg | ||
if key == "name": | ||
return self.name | ||
return None | ||
|
||
@classmethod | ||
def nativeTypeVal(cls, typename): | ||
"""Return the native type for this Neovim type.""" | ||
if typename in cls.SIMPLETYPES_VAL: | ||
return cls.SIMPLETYPES_VAL[typename] | ||
elif typename in cls.EXTTYPES: | ||
return cls.EXTTYPES[typename] | ||
elif cls.UNBOUND_ARRAY.match(typename): | ||
m = cls.UNBOUND_ARRAY.match(typename) | ||
return 'List<%s>' % cls.nativeTypeVal(m.groups()[0]) | ||
raise UnsupportedType(typename) | ||
|
||
@classmethod | ||
def nativeTypeRef(cls, typename): | ||
"""Return the native type for this Neovim type.""" | ||
if typename in cls.SIMPLETYPES_REF: | ||
return cls.SIMPLETYPES_REF[typename] | ||
elif typename in cls.EXTTYPES: | ||
return "%s" % cls.EXTTYPES[typename] | ||
elif cls.UNBOUND_ARRAY.match(typename): | ||
m = cls.UNBOUND_ARRAY.match(typename) | ||
return 'List<%s>' % cls.nativeTypeVal(m.groups()[0]) | ||
raise UnsupportedType(typename) | ||
|
||
|
||
class Function: | ||
""" | ||
Representation for a Neovim API Function | ||
""" | ||
|
||
def __init__(self, nvim_fun, all_ext_prefixes): | ||
self.valid = False | ||
self.fun = nvim_fun | ||
self.parameters = [] | ||
self.name = self.fun['name'] | ||
self.since = self.fun['since'] | ||
|
||
self.ext = self._is_ext(all_ext_prefixes) | ||
|
||
try: | ||
self.return_type = NeovimTypeVal(self.fun['return_type']) | ||
if self.ext: | ||
for param in self.fun['parameters'][1:]: | ||
self.parameters.append(NeovimTypeVal(*param)) | ||
else: | ||
for param in self.fun['parameters']: | ||
self.parameters.append(NeovimTypeVal(*param)) | ||
except UnsupportedType as ex: | ||
print('Found unsupported type(%s) when adding function %s(), skipping' % ( | ||
ex, self.name)) | ||
return | ||
|
||
# Build the argument string - makes it easier for the templates | ||
self.argstring = ', '.join( | ||
['%s %s' % (tv.native_type_arg, tv["name"]) for tv in self.parameters]) | ||
|
||
# filter function, use only nvim one | ||
# nvim_ui_attach implemented manually | ||
self.valid = self.name.startswith('nvim')\ | ||
and self.name != 'nvim_ui_attach' | ||
|
||
def _is_ext(self, all_ext_prefixes): | ||
for prefix in all_ext_prefixes: | ||
if self.name.startswith(prefix): | ||
return True | ||
return False | ||
|
||
|
||
class ExtType: | ||
|
||
"""Ext type, Buffer, Window, Tab""" | ||
|
||
def __init__(self, typename, info): | ||
self.name = typename | ||
self.id = info['id'] | ||
self.prefix = info['prefix'] | ||
|
||
|
||
def print_api(api): | ||
print(api.keys()) | ||
for key in api.keys(): | ||
if key == 'functions': | ||
print('Functions') | ||
for f in api[key]: | ||
if f['name'].startswith('nvim'): | ||
print(f) | ||
print('') | ||
elif key == 'types': | ||
print('Data Types') | ||
for typ in api[key]: | ||
print('\t%s' % typ) | ||
print('') | ||
elif key == 'error_types': | ||
print('Error Types') | ||
for err, desc in api[key].items(): | ||
print('\t%s:%d' % (err, desc['id'])) | ||
print('') | ||
elif key == 'version': | ||
print('Version') | ||
print(api[key]) | ||
print('') | ||
else: | ||
print('Unknown API info attribute: %s' % key) | ||
|
||
|
||
if __name__ == '__main__': | ||
|
||
if len(sys.argv) < 2 or len(sys.argv) > 3: | ||
print('Usage:') | ||
print('\tgenerate_bindings <nvim>') | ||
print('\tgenerate_bindings <nvim> [path]') | ||
sys.exit(-1) | ||
|
||
nvim = sys.argv[1] | ||
outpath = None if len(sys.argv) < 3 else sys.argv[2] | ||
|
||
try: | ||
api = get_api_info(sys.argv[1]) | ||
except subprocess.CalledProcessError as ex: | ||
print(ex) | ||
sys.exit(-1) | ||
|
||
if outpath: | ||
print('Writing auto generated bindings to %s' % outpath) | ||
if not os.path.exists(outpath): | ||
os.makedirs(outpath) | ||
for name in os.listdir(INPUT): | ||
if name.startswith('.'): | ||
continue | ||
if name.endswith('.dart'): | ||
env = {} | ||
env['date'] = datetime.datetime.now() | ||
|
||
exttypes = [ExtType(typename, info) | ||
for typename, info in api['types'].items()] | ||
all_ext_prefixes = {exttype.prefix for exttype in exttypes} | ||
functions = [Function(f, all_ext_prefixes) | ||
for f in api['functions']] | ||
env['functions'] = [f for f in functions if f.valid] | ||
env['exttypes'] = exttypes | ||
env['to_camel_case'] = make_camel_case | ||
env['make_args_from_params'] = make_args_from_params | ||
generate_file(name, outpath, **env) | ||
|
||
else: | ||
print('Neovim api info:') | ||
print_api(api) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Generated {{ date }} by `gen_bindings.py`. | ||
// DO NOT MODIFY DIRECTLY! | ||
|
||
import 'dart:async'; | ||
import 'package:meta/meta.dart'; | ||
import 'package:dart_nvim_api/session.dart'; | ||
|
||
{% for etype in exttypes %} | ||
class {{ etype.name }} { | ||
dynamic _codeData; | ||
/// Internal value that represents the type. | ||
get codeData => _codeData; | ||
{{ etype.name }}(this._codeData); | ||
|
||
{% for f in functions if f.ext and f.name.startswith(etype.prefix) %} | ||
/// since: {{f.since}} | ||
Future<{{ f.return_type.native_type_ret }}> {{ f.name | capitalize | replace(etype.prefix, '') }}(Neovim neovim, {{ f.argstring }}) { | ||
return neovim.session.call("{{f.name}}", | ||
args: [_codeData | ||
{% if f.parameters|count > 0 %} | ||
, {{ f.parameters|map(attribute = "name")|join(", ") }} | ||
{% endif %} | ||
]); | ||
} | ||
{% endfor %} | ||
} | ||
|
||
{% endfor %} | ||
|
||
class Neovim { | ||
Session _session; | ||
get session => _session; | ||
|
||
Neovim.fromSession(this._session); | ||
Neovim.connectToRunningInstance({ | ||
@required String host, | ||
@required int port, | ||
}) : _session = Session.fromRunningInstance(host: host, port: port); | ||
Neovim({String nvimBinaryPath}) | ||
: _session = Session(nvim: nvimBinaryPath ?? '/usr/bin/nvim'); | ||
|
||
{% for f in functions if not f.ext %} | ||
{% set trimmedFname = f.name | replace('nvim_', '') %} | ||
Future<{{ f.return_type.native_type_ret }}> {{ to_camel_case(trimmedFname) }}({{ f.argstring }}) async { | ||
var retVal = await _session.call("{{ f.name }}", | ||
args: [{{ make_args_from_params(f.parameters) | map(attribute = "name") | join(", ") }}]); | ||
|
||
{% if "Map" in f.return_type.native_type_ret or "List" in f.return_type.native_type_ret %} | ||
retVal = {{ f.return_type.native_type_ret }}.from(retVal); | ||
|
||
{% elif f.return_type.native_type_ret != "void" %} | ||
retVal = retVal as {{ f.return_type.native_type_ret }}; | ||
{% endif %} | ||
|
||
return retVal; | ||
} | ||
|
||
{% endfor %} | ||
} |
Oops, something went wrong.