Class: Neo2Vim

Inherits:
Object
  • Object
show all
Defined in:
lib/neo2vim.rb

Defined Under Namespace

Classes: PluginContent

Instance Method Summary collapse

Constructor Details

#initialize(source, destination) ⇒ Neo2Vim

Returns a new instance of Neo2Vim.



83
84
85
86
87
88
89
90
91
92
# File 'lib/neo2vim.rb', line 83

def initialize source, destination
    @names = ["autoload_function", "function", "command", "autocmd"]
    @stores = Hash[@names.map{|name| [name, {}]}]
    @annotation = nil
    @plugin_class_name = nil
    @in_plugin_class = false
    @plugin_id = nil
    @state = :normal
    run source, destination
end

Instance Method Details

#method_args(line) ⇒ Object



38
39
40
41
# File 'lib/neo2vim.rb', line 38

def method_args(line)
    start = line.index('(') + 1
    line[start..-1].scan(/\s*(\w+)(?:\s*=\s*[^,]+)?\s*(?:,|\))/).map(&:first)[1..-1]
end

#method_info(line, annotation = nil) ⇒ Object



31
32
33
34
35
36
37
# File 'lib/neo2vim.rb', line 31

def method_info(line, annotation = nil)
    {
        name: method_name(line),
        args: method_args(line),
        annotation: annotation,
    }
end

#method_name(line) ⇒ Object



52
53
54
# File 'lib/neo2vim.rb', line 52

def method_name line
    line.chomp.gsub(/^ *[^ ]* /, "").gsub(/\(.*/, "")
end

#neovim_annotation(line) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/neo2vim.rb', line 19

def neovim_annotation line
    # Supported pattern: @neovim.{type}({name}, attr1 = value1, attr2 = value2, ...)
    if line =~ /^\s*@neovim\.(\w+)\(((?:'\w+')|(?:"\w+"))(.+)/
        basic = {type: $1, name: $2[1..-2]}
        args = Hash[$3.scan(/\s*,\s*(\w+)\s*=\s*((?:'[^']*')|(?:"[^"]*"))/).map {|name, value|
          [name.to_sym, value[1..-2]]
        }]
        basic.merge(args)
    else
        nil
    end
end

#on_line(line, contents) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/neo2vim.rb', line 58

def on_line line, contents
    line.gsub!("import neovim", "import vim")
    line.gsub!(/.*if neovim$*/, "")
    contents.autoload_py.write line
    case @state
    when :plugin_class_definiton
        @plugin_class_name = line.chomp.gsub(/^class\s+(\w+).*$/, '\1')
        @plugin_id = to_snake(@plugin_class_name)
        @state = :normal
    when :plugin_method_definition
        if @annotation && @names.include?(@annotation[:type])
            @stores[@annotation[:type]][method_name(line)] = method_info(line, @annotation)
        end
        @state = :normal
    when :normal
      if @in_plugin_class && line =~ /^[^#\s]/
        # TODO: deal with multi-line string literal
        @in_plugin_class = false
      end
      if @in_plugin_class && line =~ /^\s*def\s/ && method_name(line) !~ /^__/
        @stores["autoload_function"][method_name(line)] = method_info(line)
      end
    end
    @annotation = nil
end

#on_neovim_line(line) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/neo2vim.rb', line 42

def on_neovim_line line
    @annotation = nil
    if line.include? "plugin"
        @state = :plugin_class_definiton
        @in_plugin_class = true
    else
        @annotation = neovim_annotation line
        @state = :plugin_method_definition
    end
end

#parse(path) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/neo2vim.rb', line 158

def parse path
    contents = PluginContent.new

    nvim_guard = <<-EOS
if has('nvim')
  finish
endif
    EOS

    contents.plugin.puts(nvim_guard)

    contents.autoload.puts <<-EOS
if !has('nvim')
execute 'pyfile' expand('<sfile>:p').'.py'
endif
    EOS
    contents.autoload.puts

    File.open(path) do |src|
        src.each_line do |line|
            if /^\s*@neovim/ =~ line
                on_neovim_line line
            else
                on_line line, contents
            end
        end
    end
    contents.autoload_py.puts "#{@plugin_id}_plugin = #{@plugin_class_name}(vim)"
    write_declarations(contents)

    contents.autoload.puts <<-EOS
function! s:call_plugin(method_name, args) abort
" TODO: support nvim rpc
if has('nvim')
  throw 'Call rplugin from vimscript: not supported yet'
endif
unlet! g:__error
python <<PY
try:
  r = getattr(#{@plugin_id}_plugin, vim.eval('a:method_name'))(*vim.eval('a:args'))
  vim.command('let g:__result = ' + json.dumps(([] if r == None else r)))
except:
  vim.command('let g:__error = ' + json.dumps(str(sys.exc_info()[0]) + ':' + str(sys.exc_info()[1])))
PY
if exists('g:__error')
  throw g:__error
endif
let res = g:__result
unlet g:__result
return res
endfunction
    EOS

    contents
end

#run(source, destination) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/neo2vim.rb', line 140

def run source, destination
    %w(plugin autoload).each do|dir|
        FileUtils.mkdir_p(File.join(destination, dir))
    end

    contents = File.open(source) {|f| parse f }

    File.open(File.join(destination, 'plugin', "#{@plugin_id}.vim"), "w") do |f|
        f.print(contents.plugin.string)
    end
    File.open(File.join(destination, 'autoload', "#{@plugin_id}.vim"), "w") do |f|
        f.print(contents.autoload.string)
    end
    File.open(File.join(destination, 'autoload', "#{@plugin_id}.vim.py"), "w") do |f|
        f.print(contents.autoload_py.string)
    end
end

#to_snake(name) ⇒ Object



55
56
57
# File 'lib/neo2vim.rb', line 55

def to_snake name
    name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
end

#write_declarations(contents) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/neo2vim.rb', line 93

def write_declarations contents
    ["autoload_function", "function", "autocmd", "command"].each do |what|
        @stores[what].each do |k, v|
            contents.autoload.puts <<-EOS
function! #{@plugin_id}\##{k}(#{v[:args].join(", ")}) abort
return s:call_plugin('#{k}', [#{v[:args].map {|a| "a:" + a}.join(", ")}])
endfunction
            EOS
            contents.autoload.puts
        end
    end
    contents.plugin.puts <<-EOS
augroup #{@plugin_id}
autocmd!
    EOS
    @stores["autocmd"].each do |k, v|
        contents.plugin.puts <<-EOS
autocmd #{v[:annotation][:name]} #{v[:annotation][:pattern] || '*'} call #{@plugin_id}\##{k}(#{v[:annotation][:eval] || "expand('<afile>')"})
        EOS
    end
    contents.plugin.puts "augroup END"
    contents.plugin.puts

    @stores["command"].each do |k, v|
        range =
          case v[:annotation][:range]
          when ''
            "-range"
          when nil
            ""
          else
            "-range=#{v[:annotation][:range]}"
          end
        nargs = "-nargs=#{v[:annotation][:nargs] || '0'}"
        # TODO: range argument is dummy
        contents.plugin.puts "command! #{nargs} #{range} #{v[:annotation][:name]} call #{@plugin_id}\##{k}([<f-args>], '')"
    end
    contents.plugin.puts

    @stores["function"].each do |k, v|
        contents.plugin.puts <<-EOS
function! #{v[:annotation][:name]}(...) abort
return #{@plugin_id}##{k}(a:000)
endfunction
        EOS
    end
end