neovim 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -19
  3. data/CHANGELOG.md +9 -0
  4. data/README.md +2 -2
  5. data/Rakefile +1 -25
  6. data/bin/j2mp +1 -1
  7. data/bin/mp2j +1 -1
  8. data/bin/neovim-ruby-host +9 -0
  9. data/lib/neovim.rb +5 -7
  10. data/lib/neovim/async_session.rb +2 -1
  11. data/lib/neovim/buffer.rb +112 -0
  12. data/lib/neovim/client.rb +4 -2
  13. data/lib/neovim/current.rb +9 -1
  14. data/lib/neovim/event_loop.rb +8 -4
  15. data/lib/neovim/host.rb +3 -1
  16. data/lib/neovim/line_range.rb +34 -10
  17. data/lib/neovim/logging.rb +29 -20
  18. data/lib/neovim/manifest.rb +9 -3
  19. data/lib/neovim/plugin.rb +3 -1
  20. data/lib/neovim/plugin/dsl.rb +14 -0
  21. data/lib/neovim/plugin/handler.rb +24 -5
  22. data/lib/neovim/ruby_provider.rb +138 -0
  23. data/lib/neovim/session.rb +14 -5
  24. data/lib/neovim/version.rb +1 -1
  25. data/lib/neovim/window.rb +52 -59
  26. data/spec/acceptance/neovim-ruby-host_spec.rb +6 -1
  27. data/spec/acceptance/ruby_provider_spec.rb +76 -0
  28. data/spec/helper.rb +13 -19
  29. data/spec/neovim/async_session_spec.rb +19 -15
  30. data/spec/neovim/buffer_spec.rb +127 -1
  31. data/spec/neovim/client_spec.rb +1 -1
  32. data/spec/neovim/current_spec.rb +9 -1
  33. data/spec/neovim/event_loop_spec.rb +20 -21
  34. data/spec/neovim/host_spec.rb +1 -1
  35. data/spec/neovim/line_range_spec.rb +73 -9
  36. data/spec/neovim/manifest_spec.rb +26 -0
  37. data/spec/neovim/msgpack_stream_spec.rb +8 -8
  38. data/spec/neovim/plugin_spec.rb +41 -0
  39. data/spec/neovim/remote_object_spec.rb +3 -3
  40. data/spec/neovim/session_spec.rb +68 -29
  41. data/spec/neovim/window_spec.rb +47 -24
  42. data/spec/neovim_spec.rb +3 -5
  43. data/spec/support.rb +1 -2
  44. metadata +5 -3
  45. data/.gitmodules +0 -4
@@ -30,7 +30,7 @@ module Neovim
30
30
  it "delegates messages to the manifest" do
31
31
  messages = []
32
32
  manifest = instance_double(Manifest)
33
- session = Session.child(["-n", "-u", "NONE"])
33
+ session = Session.child(["nvim", "-n", "-u", "NONE"])
34
34
 
35
35
  host = Host.new(manifest, session)
36
36
 
@@ -2,14 +2,16 @@ require "helper"
2
2
 
3
3
  module Neovim
4
4
  RSpec.describe LineRange do
5
- let(:client) { Neovim.attach_child(["--headless", "-n", "-u", "NONE"]) }
5
+ let(:client) { Neovim.attach_child(["nvim", "--headless", "-n", "-u", "NONE"]) }
6
6
  let(:buffer) { client.current.buffer }
7
- let(:line_range) { LineRange.new(buffer, 0, -1) }
7
+ let(:line_range) { LineRange.new(buffer, 0, 3) }
8
+ let(:sub_range) { LineRange.new(buffer, 1, 2) }
8
9
 
9
10
  before do
10
11
  client.command("normal i1")
11
12
  client.command("normal o2")
12
13
  client.command("normal o3")
14
+ client.command("normal o4")
13
15
  end
14
16
 
15
17
  it "is enumerable" do
@@ -19,7 +21,11 @@ module Neovim
19
21
 
20
22
  describe "#to_a" do
21
23
  it "returns lines as an array" do
22
- expect(line_range.to_a).to eq(["1", "2", "3"])
24
+ expect(line_range.to_a).to eq(["1", "2", "3", "4"])
25
+ end
26
+
27
+ it "returns a subset of lines as an array" do
28
+ expect(sub_range.to_a).to eq(["2", "3"])
23
29
  end
24
30
  end
25
31
 
@@ -28,40 +34,98 @@ module Neovim
28
34
  expect(line_range[1]).to eq("2")
29
35
  end
30
36
 
37
+ it "returns lines at an offset from the index" do
38
+ expect(sub_range[0]).to eq("2")
39
+ end
40
+
41
+ it "allows indexes beyond the bounds of a sub range" do
42
+ expect(sub_range[2]).to eq("4")
43
+ end
44
+
45
+ it "returns lines at an offset with a negative index" do
46
+ expect(sub_range[-1]).to eq("3")
47
+ end
48
+
31
49
  it "accepts an index and length" do
32
50
  expect(line_range[0, 2].to_a).to eq(["1", "2"])
33
51
  end
34
52
 
53
+ it "returns lines at an offset from an index and length" do
54
+ expect(sub_range[0, 2].to_a).to eq(["2", "3"])
55
+ end
56
+
35
57
  it "accepts a range" do
36
58
  expect(line_range[0..1].to_a).to eq(["1", "2"])
37
59
  expect(line_range[0...1].to_a).to eq(["1"])
38
60
  end
61
+
62
+ it "accepts a range with a negative end" do
63
+ expect(line_range[0..-1].to_a).to eq(["1", "2", "3", "4"])
64
+ end
65
+
66
+ it "returns lines at an offset from a range" do
67
+ expect(sub_range[0..1].to_a).to eq(["2", "3"])
68
+ end
39
69
  end
40
70
 
41
71
  describe "#[]=" do
42
72
  it "accepts a single index" do
43
73
  line_range[0] = "foo"
44
- expect(line_range.to_a).to eq(["foo", "2", "3"])
74
+ expect(line_range.to_a).to eq(["foo", "2", "3", "4"])
75
+ end
76
+
77
+ it "accepts a single index at an offset" do
78
+ sub_range[0] = "foo"
79
+ expect(buffer.lines.to_a).to eq(["1", "foo", "3", "4"])
45
80
  end
46
81
 
47
82
  it "accepts an index and length" do
48
83
  line_range[0, 2] = ["foo"]
49
- expect(line_range.to_a).to eq(["foo", "3"])
84
+ expect(line_range.to_a).to eq(["foo", "3", "4"])
85
+ end
86
+
87
+ it "accepts an index and length at an offset" do
88
+ sub_range[0, 2] = ["foo"]
89
+ expect(buffer.lines.to_a).to eq(["1", "foo", "4"])
50
90
  end
51
91
 
52
92
  it "accepts a range" do
53
93
  line_range[0..1] = ["foo"]
54
- expect(line_range.to_a).to eq(["foo", "3"])
94
+ expect(line_range.to_a).to eq(["foo", "3", "4"])
55
95
 
56
96
  line_range[0...1] = ["bar"]
57
- expect(line_range.to_a).to eq(["bar", "3"])
97
+ expect(line_range.to_a).to eq(["bar", "3", "4"])
98
+ end
99
+
100
+ it "accepts a range at an offset" do
101
+ sub_range[0..1] = ["foo"]
102
+ expect(buffer.lines.to_a).to eq(["1", "foo", "4"])
58
103
  end
59
104
  end
60
105
 
61
106
  describe "#replace" do
62
107
  it "replaces all lines" do
63
- line_range.replace(["5", "6"])
64
- expect(line_range.to_a).to eq(["5", "6"])
108
+ line_range.replace(["4", "5"])
109
+ expect(line_range.to_a).to eq(["4", "5"])
110
+ end
111
+
112
+ it "replaces a subset of lines" do
113
+ sub_range.replace(["5", "6"])
114
+ expect(buffer.lines.to_a).to eq(["1", "5", "6", "4"])
115
+ end
116
+ end
117
+
118
+ describe "#delete" do
119
+ it "deletes the line at the given index" do
120
+ expect {
121
+ line_range.delete(0)
122
+ }.to change { line_range.to_a }.to(["2", "3", "4"])
123
+ end
124
+
125
+ it "deletes the line at an offset" do
126
+ expect {
127
+ sub_range.delete(0)
128
+ }.to change { buffer.lines.to_a }.to(["1", "3", "4"])
65
129
  end
66
130
  end
67
131
  end
@@ -38,6 +38,18 @@ module Neovim
38
38
  manifest.handlers["source:command:Foo"]
39
39
  }.from(nil).to(kind_of(Proc))
40
40
  end
41
+
42
+ it "doesn't add top-level RPCs to specs" do
43
+ manifest = Manifest.new
44
+
45
+ plugin = Plugin.from_config_block("source") do |plug|
46
+ plug.rpc(:Foo)
47
+ end
48
+
49
+ expect {
50
+ manifest.register(plugin)
51
+ }.to change { manifest.specs }.from({}).to("source" => [])
52
+ end
41
53
  end
42
54
 
43
55
  describe "#handle" do
@@ -77,6 +89,20 @@ module Neovim
77
89
  manifest.handle(message, client)
78
90
  end
79
91
 
92
+ it "rescues plugin sync handler exceptions" do
93
+ manifest = Manifest.new
94
+ plugin = Plugin.from_config_block("source") do |plug|
95
+ plug.command(:Foo, :sync => true) { raise "BOOM" }
96
+ end
97
+ manifest.register(plugin)
98
+
99
+ message = double(:message, :method_name => "source:command:Foo", :sync? => true, :arguments => [])
100
+ client = double(:client)
101
+
102
+ expect(message).to receive(:error).with("BOOM")
103
+ manifest.handle(message, client)
104
+ end
105
+
80
106
  it "calls a plugin async handler" do
81
107
  manifest = Manifest.new
82
108
  async_proc = Proc.new {}
@@ -6,26 +6,26 @@ module Neovim
6
6
  shared_context "msgpack stream behavior" do
7
7
  it "sends and receives data" do
8
8
  msgpack_stream = MsgpackStream.new(event_loop)
9
- client_messages = []
9
+ request = nil
10
10
 
11
11
  server_thread = Thread.new do
12
12
  client = server.accept
13
- client_messages << client.readpartial(1024)
13
+ request = client.readpartial(1024)
14
14
 
15
- client.write(MessagePack.pack([2]))
15
+ client.write(MessagePack.pack(["res"]))
16
16
  client.close
17
17
  server.close
18
18
  end
19
19
 
20
- server_message = nil
21
- msgpack_stream.write([1]).run do |message|
22
- server_message = message
20
+ response = nil
21
+ msgpack_stream.write(["req"]).run do |message|
22
+ response = message
23
23
  msgpack_stream.shutdown
24
24
  end
25
25
 
26
26
  server_thread.join
27
- expect(server_message).to eq([2])
28
- expect(client_messages).to eq([MessagePack.pack([1])])
27
+ expect(request).to eq(MessagePack.pack(["req"]))
28
+ expect(response).to eq(["res"])
29
29
  end
30
30
  end
31
31
 
@@ -62,6 +62,47 @@ module Neovim
62
62
  :opts => {:range => "", :nargs => 1},
63
63
  )
64
64
  end
65
+
66
+ it "registers a top level RPC" do
67
+ cmd_block = Proc.new {}
68
+
69
+ plugin = Plugin.from_config_block("source") do |plug|
70
+ plug.rpc("Foo", :sync => true, &cmd_block)
71
+ end
72
+
73
+ expect(plugin.handlers.size).to be(1)
74
+ handler = plugin.handlers.first
75
+
76
+ expect(handler.block).to eq(cmd_block)
77
+ expect(handler.qualified_name).to eq("Foo")
78
+ end
79
+ end
80
+
81
+ describe "#specs" do
82
+ it "returns specs for plugin handlers" do
83
+ plugin = Plugin.from_config_block("source") do |plug|
84
+ plug.command("Foo", :sync => true, :nargs => 2)
85
+ end
86
+
87
+ expect(plugin.specs).to eq(
88
+ [
89
+ {
90
+ :type => :command,
91
+ :name => "Foo",
92
+ :sync => true,
93
+ :opts=> {:nargs => 2}
94
+ }
95
+ ]
96
+ )
97
+ end
98
+
99
+ it "doesn't include specs for top-level RPCs" do
100
+ plugin = Plugin.from_config_block("source") do |plug|
101
+ plug.rpc("Foo", :sync => true)
102
+ end
103
+
104
+ expect(plugin.specs).to eq([])
105
+ end
65
106
  end
66
107
  end
67
108
  end
@@ -3,7 +3,7 @@ require "helper"
3
3
  module Neovim
4
4
  RSpec.describe RemoteObject do
5
5
  context Window do
6
- let(:window) { Neovim.attach_child(["-n", "-u", "NONE"]).current.window }
6
+ let(:window) { Neovim.attach_child(["nvim", "-n", "-u", "NONE"]).current.window }
7
7
 
8
8
  describe "#respond_to?" do
9
9
  it "returns true for Window functions" do
@@ -37,7 +37,7 @@ module Neovim
37
37
  end
38
38
 
39
39
  context Tabpage do
40
- let(:tabpage) { Neovim.attach_child(["-n", "-u", "NONE"]).current.tabpage }
40
+ let(:tabpage) { Neovim.attach_child(["nvim", "-n", "-u", "NONE"]).current.tabpage }
41
41
 
42
42
  describe "#respond_to?" do
43
43
  it "returns true for Tabpage functions" do
@@ -71,7 +71,7 @@ module Neovim
71
71
  end
72
72
 
73
73
  context Buffer do
74
- let(:buffer) { Neovim.attach_child(["-n", "-u", "NONE"]).current.buffer }
74
+ let(:buffer) { Neovim.attach_child(["nvim", "-n", "-u", "NONE"]).current.buffer }
75
75
 
76
76
  describe "#respond_to?" do
77
77
  it "returns true for Buffer functions" do
@@ -1,43 +1,84 @@
1
1
  require "helper"
2
2
  require "securerandom"
3
- require "fileutils"
4
3
 
5
4
  module Neovim
6
5
  RSpec.describe Session do
7
6
  shared_context "session behavior" do
8
- it "supports requests" do
9
- expect(session.request(:vim_strwidth, "foobar")).to be(6)
10
- end
7
+ describe "#channel_id" do
8
+ it "returns nil when the API hasn't been discovered" do
9
+ expect(session.channel_id).to be(nil)
10
+ end
11
11
 
12
- it "supports notifications" do
13
- expect(session.notify(:vim_input, "jk")).to be(nil)
12
+ it "returns the channel_id when the API has been discovered" do
13
+ session.discover_api
14
+ expect(session.channel_id).to respond_to(:to_int)
15
+ end
14
16
  end
15
17
 
16
- it "raises an exception when there are errors" do
17
- expect {
18
- session.request(:vim_strwidth, "too", "many")
19
- }.to raise_error(/wrong number of arguments/i)
18
+ describe "#request" do
19
+ it "synchronously returns a result" do
20
+ expect(session.request(:vim_strwidth, "foobar")).to be(6)
21
+ end
22
+
23
+ it "raises an exception when there are errors" do
24
+ expect {
25
+ session.request(:vim_strwidth, "too", "many")
26
+ }.to raise_error(/wrong number of arguments/i)
27
+ end
28
+
29
+ it "handles large data" do
30
+ large_str = Array.new(1024 * 17) { SecureRandom.hex(1) }.join
31
+ session.request(:vim_set_current_line, large_str)
32
+ expect(session.request(:vim_get_current_line)).to eq(large_str)
33
+ end
20
34
  end
21
35
 
22
- it "handles large data" do
23
- large_str = Array.new(1024 * 16) { SecureRandom.hex(1) }.join
24
- session.request(:vim_set_current_line, large_str)
25
- expect(session.request(:vim_get_current_line)).to eq(large_str)
36
+ describe "#notify" do
37
+ it "returns nil" do
38
+ expect(session.notify(:vim_input, "jk")).to be(nil)
39
+ end
40
+
41
+ it "doesn't raise exceptions" do
42
+ expect {
43
+ session.notify(:vim_strwidth, "too", "many")
44
+ }.not_to raise_error
45
+ end
46
+
47
+ it "handles large data" do
48
+ large_str = Array.new(1024 * 17) { SecureRandom.hex(1) }.join
49
+ session.notify(:vim_set_current_line, large_str)
50
+ expect(session.request(:vim_get_current_line)).to eq(large_str)
51
+ end
26
52
  end
27
53
 
28
- it "subscribes to events" do
29
- session.request(:vim_subscribe, "my_event")
30
- session.request(:vim_command, "call rpcnotify(0, 'my_event', 'foo')")
54
+ describe "#run" do
55
+ it "enqueues messages received during blocking requests" do
56
+ session.request(:vim_subscribe, "my_event")
57
+ session.request(:vim_command, "call rpcnotify(0, 'my_event', 'foo')")
31
58
 
32
- messages = []
33
- session.run do |msg|
34
- messages << msg
35
- session.shutdown
59
+ message = nil
60
+ session.run do |msg|
61
+ message = msg
62
+ session.shutdown
63
+ end
64
+
65
+ expect(message).to be_a(Notification)
66
+ expect(message.method_name).to eq("my_event")
67
+ expect(message.arguments).to eq(["foo"])
36
68
  end
37
69
 
38
- expect(messages.first).to be_a(Notification)
39
- expect(messages.first.method_name).to eq("my_event")
40
- expect(messages.first.arguments).to eq(["foo"])
70
+ it "supports requests within callbacks" do
71
+ session.request(:vim_subscribe, "my_event")
72
+ session.request(:vim_command, "call rpcnotify(0, 'my_event', 'foo')")
73
+
74
+ result = nil
75
+ session.run do |msg|
76
+ result = session.request(:vim_strwidth, msg.arguments.first)
77
+ session.shutdown
78
+ end
79
+
80
+ expect(result).to be(3)
81
+ end
41
82
  end
42
83
  end
43
84
 
@@ -46,7 +87,7 @@ module Neovim
46
87
  let!(:nvim_pid) do
47
88
  pid = Process.spawn(
48
89
  {"NVIM_LISTEN_ADDRESS" => "0.0.0.0:#{nvim_port}"},
49
- "#{ENV.fetch("NVIM_EXECUTABLE")} --headless -n -u NONE",
90
+ "nvim --headless -n -u NONE",
50
91
  [:out, :err] => "/dev/null"
51
92
  )
52
93
 
@@ -65,7 +106,6 @@ module Neovim
65
106
  end
66
107
 
67
108
  let(:session) { Session.tcp("0.0.0.0", nvim_port) }
68
-
69
109
  include_context "session behavior"
70
110
  end
71
111
 
@@ -74,7 +114,7 @@ module Neovim
74
114
  let!(:nvim_pid) do
75
115
  pid = Process.spawn(
76
116
  {"NVIM_LISTEN_ADDRESS" => socket_path},
77
- "#{ENV.fetch("NVIM_EXECUTABLE")} --headless -n -u NONE",
117
+ "nvim --headless -n -u NONE",
78
118
  [:out, :err] => "/dev/null"
79
119
  )
80
120
 
@@ -93,12 +133,11 @@ module Neovim
93
133
  end
94
134
 
95
135
  let(:session) { Session.unix(socket_path) }
96
-
97
136
  include_context "session behavior"
98
137
  end
99
138
 
100
139
  context "child" do
101
- let(:session) { Session.child(["-n", "-u", "NONE"]) }
140
+ let(:session) { Session.child(["nvim", "-n", "-u", "NONE"]) }
102
141
  include_context "session behavior"
103
142
  end
104
143
  end
@@ -2,39 +2,62 @@ require "helper"
2
2
 
3
3
  module Neovim
4
4
  RSpec.describe Window do
5
- let(:client) { Neovim.attach_child(["--headless", "-n", "-u", "NONE"]) }
5
+ let(:client) { Neovim.attach_child(["nvim", "--headless", "-n", "-u", "NONE"]) }
6
6
  let(:window) { client.current.window }
7
7
 
8
- before do
9
- client.command("normal 3Oabc")
10
- client.command("normal gg")
11
- end
8
+ describe "if_ruby compatibility" do
9
+ describe "#buffer" do
10
+ it "returns the buffer displayed in the window" do
11
+ expect(window.buffer).to be_a(Buffer)
12
+ end
13
+ end
14
+
15
+ describe "#height" do
16
+ it "returns the height of the window" do
17
+ client.set_option("lines", 5)
18
+ expect(window.height).to be(3)
19
+ end
20
+ end
12
21
 
13
- describe "#cursor" do
14
- it "moves line-wise" do
15
- expect {
16
- window.cursor.line += 1
17
- }.to change { window.cursor.coordinates }.from([1, 0]).to([2, 0])
22
+ describe "#height=" do
23
+ it "sets the height of the window" do
24
+ expect {
25
+ window.height = 5
26
+ }.to change { window.height }.to(5)
27
+ end
28
+ end
18
29
 
19
- expect {
20
- window.cursor.line -= 1
21
- }.to change { window.cursor.coordinates }.from([2, 0]).to([1, 0])
30
+ describe "#width" do
31
+ it "returns the width of the window" do
32
+ client.set_option("columns", 20)
33
+ expect(window.width).to be(20)
34
+ end
22
35
  end
23
36
 
24
- it "moves column-wise" do
25
- expect {
26
- window.cursor.column += 1
27
- }.to change { window.cursor.coordinates }.from([1, 0]).to([1, 1])
37
+ describe "#width=" do
38
+ it "sets the width of a vertically split window" do
39
+ client.command("vsplit")
40
+
41
+ expect {
42
+ window.width += 1
43
+ }.to change { window.width }.by(1)
44
+ end
45
+ end
28
46
 
29
- expect {
30
- window.cursor.column -= 1
31
- }.to change { window.cursor.coordinates }.from([1, 1]).to([1, 0])
47
+ describe "#cursor" do
48
+ it "returns the cursor coordinates" do
49
+ expect(window.cursor).to eq([1, 0])
50
+ end
32
51
  end
33
52
 
34
- it "moves to an absolute position" do
35
- expect {
36
- window.cursor.coordinates = [1, 1]
37
- }.to change { window.cursor.coordinates }.to([1, 1])
53
+ describe "#cursor=" do
54
+ it "sets the cursor coodinates" do
55
+ window.buffer.lines = ["one", "two"]
56
+
57
+ expect {
58
+ window.cursor = [2, 2]
59
+ }.to change { window.cursor }.to([2, 2])
60
+ end
38
61
  end
39
62
  end
40
63
  end