nvim 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/INFO.yaml +18 -0
- data/LICENSE +52 -0
- data/README.md +264 -0
- data/Rakefile +68 -0
- data/bin/neovim-ruby-host +51 -0
- data/lib/neovim/client.rb +108 -0
- data/lib/neovim/connection.rb +129 -0
- data/lib/neovim/foreign/mplight/bufferio.rb +51 -0
- data/lib/neovim/foreign/mplight.rb +327 -0
- data/lib/neovim/foreign/supplement/socket.rb +20 -0
- data/lib/neovim/foreign/supplement.rb +34 -0
- data/lib/neovim/handler.rb +139 -0
- data/lib/neovim/host.rb +87 -0
- data/lib/neovim/info.rb +10 -0
- data/lib/neovim/logging.rb +249 -0
- data/lib/neovim/messager.rb +185 -0
- data/lib/neovim/meta.rb +68 -0
- data/lib/neovim/remote.rb +56 -0
- data/lib/neovim/remote_object.rb +331 -0
- data/lib/neovim/ruby_provider.rb +372 -0
- data/lib/neovim/session.rb +60 -0
- data/lib/neovim/vimscript_provider.rb +49 -0
- data/lib/neovim.rb +22 -0
- metadata +66 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
#
|
2
|
+
# neovim/logging.rb -- Logging facility
|
3
|
+
#
|
4
|
+
|
5
|
+
require "neovim/foreign/supplement"
|
6
|
+
|
7
|
+
|
8
|
+
module Neovim
|
9
|
+
|
10
|
+
module Logging
|
11
|
+
|
12
|
+
class Logger
|
13
|
+
|
14
|
+
class <<self
|
15
|
+
|
16
|
+
private :new
|
17
|
+
|
18
|
+
SUBS = []
|
19
|
+
|
20
|
+
def inherited cls
|
21
|
+
SUBS.push cls
|
22
|
+
end
|
23
|
+
|
24
|
+
def provide str
|
25
|
+
opened = nil
|
26
|
+
args = str.to_s.split ":"
|
27
|
+
args.each { |a| a.gsub! %r/%(\h\h)/ do ($1.to_i 0x10).chr end }
|
28
|
+
cls =
|
29
|
+
if args.first =~ /\A\w+\z/ then
|
30
|
+
prot = args.shift
|
31
|
+
(SUBS.find { |s| s::NAME == prot rescue nil }) or raise "Logger not found: #{str}"
|
32
|
+
else
|
33
|
+
Text
|
34
|
+
end
|
35
|
+
dest = args.shift
|
36
|
+
cls.open dest, **(parse_arguments args) do |i|
|
37
|
+
opened = true
|
38
|
+
yield i
|
39
|
+
end
|
40
|
+
rescue # Errno::EACCES, Errno::ENOENT
|
41
|
+
raise unless not opened and str.notempty?
|
42
|
+
$stderr.puts "Failed to open log file '#{str}'. Logging to stderr."
|
43
|
+
str = nil
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse_arguments args
|
50
|
+
r = {}
|
51
|
+
args.each { |a|
|
52
|
+
k, v = a.split "=", 2
|
53
|
+
r[ k.to_sym] = parse_value v
|
54
|
+
}
|
55
|
+
r
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_value val
|
59
|
+
if val.nil? then
|
60
|
+
true
|
61
|
+
elsif val =~ /\A(?:0x)?\d+\z/ then
|
62
|
+
Integer val
|
63
|
+
else
|
64
|
+
case val.downcase
|
65
|
+
when "true", "yes", "on" then true
|
66
|
+
when "false", "no", "off" then false
|
67
|
+
else val.notempty?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
class Null < Logger
|
77
|
+
NAME = "null"
|
78
|
+
class <<self
|
79
|
+
def open dest = nil
|
80
|
+
yield new
|
81
|
+
end
|
82
|
+
end
|
83
|
+
def put **fields
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Stream < Logger
|
88
|
+
|
89
|
+
class <<self
|
90
|
+
def open path = nil, **kwargs
|
91
|
+
if path.notempty? and path != "-" then
|
92
|
+
params = {}
|
93
|
+
%i(external_encoding newline).each do |k|
|
94
|
+
v = kwargs.delete k
|
95
|
+
params[ k] = v if v
|
96
|
+
end
|
97
|
+
File.open path, "a", **params do |f|
|
98
|
+
yield (new f, **kwargs)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
yield (new $stderr, **kwargs)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def initialize file, **kwargs
|
107
|
+
@file = file
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class Text < Stream
|
113
|
+
NAME = "file"
|
114
|
+
def initialize file, color: nil, short: nil
|
115
|
+
super
|
116
|
+
@color =
|
117
|
+
case color
|
118
|
+
when true, false then color
|
119
|
+
when 0 then false
|
120
|
+
when Integer then true
|
121
|
+
else @file.tty?
|
122
|
+
end
|
123
|
+
@short = short
|
124
|
+
end
|
125
|
+
COLORS = %w(33 32 34;1 4 31;1 35;1 36)
|
126
|
+
def put **fields
|
127
|
+
put_sep
|
128
|
+
l = [
|
129
|
+
((fields.delete :time).strftime "%H:%M:%S"),
|
130
|
+
((fields.delete :pid).to_s.rjust 5),
|
131
|
+
(fields.delete :caller).to_s[ %r([^/]+:\d+)],
|
132
|
+
(fields.delete :level),
|
133
|
+
(fields.delete :message).inspect,
|
134
|
+
((fields.delete :class).to_s.sub /.*::/, ""),
|
135
|
+
((fields.map { |k,v| "#{k}:#{v}" }.join " ").axe 256),
|
136
|
+
]
|
137
|
+
if @color then
|
138
|
+
l = l.zip COLORS
|
139
|
+
l.map! do |f,c| "\e[#{c}m#{f}\e[m" end
|
140
|
+
end
|
141
|
+
if @short then
|
142
|
+
s = l.shift 3
|
143
|
+
if not @nexttime or @nexttime < Time.now then
|
144
|
+
@file.puts s.join " "
|
145
|
+
@nexttime = Time.now + 120
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@file.puts l.join " "
|
149
|
+
@file.flush
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
def put_sep
|
153
|
+
if @file.tty? then
|
154
|
+
if not @nextsep or @nextsep < Time.now then
|
155
|
+
@file.puts $/*5
|
156
|
+
@nextsep = Time.now + 300
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Plain < Stream
|
163
|
+
NAME = "plain"
|
164
|
+
def put **fields
|
165
|
+
@file.puts fields.to_s
|
166
|
+
@file.flush
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class Json < Stream
|
171
|
+
NAME = "json"
|
172
|
+
def initialize file
|
173
|
+
super
|
174
|
+
require "json"
|
175
|
+
require "time"
|
176
|
+
end
|
177
|
+
def put **fields
|
178
|
+
fields[ :time] = fields[ :time].iso8601 rescue nil
|
179
|
+
@file.puts fields.to_json
|
180
|
+
@file.flush
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
LEVELS = {}
|
186
|
+
%i(panic fatal error warn info debug1 debug2 debug3).each_with_index { |l,i| LEVELS[ l] = i }
|
187
|
+
LEVELS.default = LEVELS.length
|
188
|
+
DEFAULT_LEVEL = :warn
|
189
|
+
|
190
|
+
class <<self
|
191
|
+
|
192
|
+
attr_reader :level
|
193
|
+
attr_accessor :channel
|
194
|
+
|
195
|
+
def level= l
|
196
|
+
@level = l.to_sym.downcase
|
197
|
+
rescue NoMethodError
|
198
|
+
l = l.to_s
|
199
|
+
retry
|
200
|
+
end
|
201
|
+
|
202
|
+
def put level, message, **kwargs
|
203
|
+
return unless @channel
|
204
|
+
return if LEVELS[ level] > LEVELS[ @level]
|
205
|
+
@channel.put time: Time.now, pid: $$, level: level, message: message, **kwargs
|
206
|
+
nil
|
207
|
+
rescue
|
208
|
+
$stderr.puts "Failed to log: #$! (#{$!.class})"
|
209
|
+
$stderr.puts $@
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
def log level, message, **kwargs
|
218
|
+
Logging.put level, message,
|
219
|
+
class: self.class, caller: (caller 1, 1).first,
|
220
|
+
**kwargs
|
221
|
+
end
|
222
|
+
|
223
|
+
def log_exception level
|
224
|
+
Logging.put level, "Exception: #$!",
|
225
|
+
class: self.class, caller: (caller 1, 1).first,
|
226
|
+
exception: $!.class
|
227
|
+
$@.each { |b|
|
228
|
+
Logging.put :debug3, "Backtrace", line: b
|
229
|
+
}
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
def open_logfile level: nil, path: nil
|
235
|
+
level ||= ENV[ "NVIM_RUBY_LOG_LEVEL"].notempty?
|
236
|
+
path ||= ENV[ "NVIM_RUBY_LOG_FILE" ].notempty?
|
237
|
+
Logger.provide path do |l|
|
238
|
+
ov, Logging.level = Logging.level, level||DEFAULT_LEVEL
|
239
|
+
ol, Logging.channel = Logging.channel, l
|
240
|
+
yield
|
241
|
+
ensure
|
242
|
+
Logging.level, Logging.channel = ov, ol
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
@@ -0,0 +1,185 @@
|
|
1
|
+
#
|
2
|
+
# neovim/messager.rb -- Send and Receive Messages
|
3
|
+
#
|
4
|
+
|
5
|
+
require "neovim/foreign/supplement"
|
6
|
+
|
7
|
+
require "neovim/logging"
|
8
|
+
|
9
|
+
|
10
|
+
module Neovim
|
11
|
+
|
12
|
+
class Messager
|
13
|
+
|
14
|
+
class Message
|
15
|
+
|
16
|
+
@subs, @subh = [], {}
|
17
|
+
|
18
|
+
class <<self
|
19
|
+
|
20
|
+
def from_array ary
|
21
|
+
kind, *payload = *ary
|
22
|
+
klass = find kind
|
23
|
+
klass[ *payload]
|
24
|
+
end
|
25
|
+
|
26
|
+
def inherited cls ; @subs.push cls ; end
|
27
|
+
def find id
|
28
|
+
@subh[ id] ||= @subs.find { |c| c::ID == id }
|
29
|
+
end
|
30
|
+
|
31
|
+
alias [] new
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize *args
|
36
|
+
z = self.class::KEYS.zip args
|
37
|
+
@cont = z.inject Hash.new do |c,(h,k)| c[h] = k ; c end
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
c = self.class.name.sub /.*::/, ""
|
42
|
+
"#<#{c} #@cont>"
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
c = self.class.name.sub /.*::/, ""
|
47
|
+
j = @cont.map { |k,v| "#{k}:#{v}" if v }.compact.join ","
|
48
|
+
"#{c}(#{j})"
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing sym, *args
|
52
|
+
if @cont.key? sym then @cont[ sym] else super end
|
53
|
+
end
|
54
|
+
|
55
|
+
def respond_to_missing? sym, priv = nil
|
56
|
+
@cont.key? sym.to_sym
|
57
|
+
end
|
58
|
+
|
59
|
+
def methods *args
|
60
|
+
super.concat @cont.keys
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_h ; @cont ; end
|
64
|
+
|
65
|
+
def fields ; @cont.fetch_values *self.class::KEYS ; end
|
66
|
+
|
67
|
+
def to_a
|
68
|
+
[self.class::ID, *fields]
|
69
|
+
end
|
70
|
+
|
71
|
+
class Request < Message
|
72
|
+
ID = 0
|
73
|
+
KEYS = %i(request_id method_name arguments)
|
74
|
+
end
|
75
|
+
|
76
|
+
class Response < Message
|
77
|
+
ID = 1
|
78
|
+
KEYS = %i(request_id error value)
|
79
|
+
def initialize *args
|
80
|
+
super
|
81
|
+
e = @cont[ :error]
|
82
|
+
if e and not Array === e then
|
83
|
+
@cont[ :error] = [0, e]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Notification < Message
|
90
|
+
ID = 2
|
91
|
+
KEYS = %i(method_name arguments)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class ResponseError < StandardError ; end
|
97
|
+
|
98
|
+
class Disconnected < RuntimeError
|
99
|
+
def initialize
|
100
|
+
super "Lost connection to nvim process"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
include Logging
|
106
|
+
|
107
|
+
def initialize conn, session
|
108
|
+
@conn, @session = conn, session
|
109
|
+
@request_id = 0
|
110
|
+
@responses = {}
|
111
|
+
end
|
112
|
+
|
113
|
+
def run until_id = nil
|
114
|
+
loop do
|
115
|
+
message = get
|
116
|
+
case message
|
117
|
+
when Message::Response then
|
118
|
+
if @responses.key? message.request_id then
|
119
|
+
@responses[ message.request_id] = message
|
120
|
+
else
|
121
|
+
log :warning, "Dropped response", message.request_id
|
122
|
+
end
|
123
|
+
when Message::Request then
|
124
|
+
begin
|
125
|
+
r = @session.execute_handler message.method_name, message.arguments
|
126
|
+
log :debug1, "Request result", result: r
|
127
|
+
rescue
|
128
|
+
e = [ 0, $!.to_s]
|
129
|
+
log_exception :error
|
130
|
+
end
|
131
|
+
rsp = Message::Response.new message.request_id, e, r
|
132
|
+
put rsp
|
133
|
+
when Message::Notification then
|
134
|
+
begin
|
135
|
+
@session.execute_handler message.method_name, message.arguments
|
136
|
+
rescue
|
137
|
+
log_exception :error
|
138
|
+
end
|
139
|
+
end
|
140
|
+
break if until_id and @responses[ until_id]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def request method, *args
|
145
|
+
@request_id += 1
|
146
|
+
put Message::Request[ @request_id, method, args]
|
147
|
+
@responses[ @request_id] = nil
|
148
|
+
run @request_id
|
149
|
+
r = @responses.delete @request_id
|
150
|
+
if r.error then
|
151
|
+
t, e = *r.error
|
152
|
+
t = @conn.error t
|
153
|
+
raise ResponseError, "#{t}: #{e}"
|
154
|
+
end
|
155
|
+
r.value
|
156
|
+
end
|
157
|
+
|
158
|
+
def notify method, *args
|
159
|
+
put Message::Notification[ method, args]
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def put msg
|
165
|
+
log :debug1, "Sending Message", data: msg
|
166
|
+
@conn.put msg.to_a
|
167
|
+
self
|
168
|
+
rescue Errno::EPIPE
|
169
|
+
raise Disconnected
|
170
|
+
end
|
171
|
+
|
172
|
+
def get
|
173
|
+
IO.select [@conn.input], nil, nil
|
174
|
+
raise Disconnected if @conn.eof?
|
175
|
+
msg = Message.from_array @conn.get
|
176
|
+
log :debug1, "Received Message", data: msg
|
177
|
+
msg
|
178
|
+
rescue EOFError
|
179
|
+
raise Disconnected
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
data/lib/neovim/meta.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#
|
2
|
+
# neovim/meta.rb -- Metadata: Version info etc.
|
3
|
+
#
|
4
|
+
|
5
|
+
module Neovim
|
6
|
+
|
7
|
+
class Meta
|
8
|
+
|
9
|
+
def initialize name, **params
|
10
|
+
@name, @params = name, params
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
|
15
|
+
%i(
|
16
|
+
commit
|
17
|
+
version
|
18
|
+
license
|
19
|
+
summary
|
20
|
+
description
|
21
|
+
homepage
|
22
|
+
authors
|
23
|
+
email
|
24
|
+
metadata
|
25
|
+
).each { |p|
|
26
|
+
define_method p do @params[ p] end
|
27
|
+
}
|
28
|
+
|
29
|
+
alias website homepage
|
30
|
+
|
31
|
+
|
32
|
+
def version_h
|
33
|
+
@params[ :version] =~ /\Av?(\d+)(?:\.(\d+)(?:\.(\d+)))(?:-(.*))?\z/
|
34
|
+
{
|
35
|
+
major: $1,
|
36
|
+
minor: $2,
|
37
|
+
patch: $3,
|
38
|
+
prerelease: $4,
|
39
|
+
commit: @params[ :commit],
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def version_a
|
44
|
+
version_h.values_at :major, :minor, :patch, :prerelease
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes
|
48
|
+
{
|
49
|
+
website: @params[ :homepage],
|
50
|
+
license: @params[ :license ],
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def mk_gemspec spec
|
56
|
+
spec.name = @name
|
57
|
+
spec.version = @params[:version ]
|
58
|
+
spec.authors = @params[:authors ]
|
59
|
+
spec.email = @params[:email ]
|
60
|
+
spec.summary = @params[:summary ]
|
61
|
+
spec.homepage = @params[:homepage]
|
62
|
+
spec.license = @params[:license ]
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# neovim/remote.rb -- Host for Neovim
|
3
|
+
#
|
4
|
+
|
5
|
+
require "neovim/session"
|
6
|
+
require "neovim/handler"
|
7
|
+
|
8
|
+
|
9
|
+
module Neovim
|
10
|
+
|
11
|
+
class Remote < Session
|
12
|
+
|
13
|
+
class <<self
|
14
|
+
|
15
|
+
def start_client *args
|
16
|
+
start *args do |i|
|
17
|
+
yield i.start
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize conn
|
24
|
+
super
|
25
|
+
@plugins = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
@conn.start @comm, client_name, self.class.name.downcase.to_sym, client_methods
|
30
|
+
@conn.client
|
31
|
+
end
|
32
|
+
|
33
|
+
def client_name ; "ruby-client" ; end
|
34
|
+
def client_methods ; end
|
35
|
+
|
36
|
+
|
37
|
+
def add_plugins source, plugins
|
38
|
+
@plugins[ source] = plugins
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_handler name, args
|
42
|
+
@plugins.each_value do |plugin|
|
43
|
+
handler = plugin.get_handler name
|
44
|
+
if handler then
|
45
|
+
log :info, "Found handler", name: name
|
46
|
+
log :debug1, "Calling with", args: args
|
47
|
+
return handler.execute @conn.client, *args
|
48
|
+
end
|
49
|
+
end
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|