Skip to content
This repository

Better Vim integration #44

Open
wants to merge 1 commit into from

5 participants

Willy Blandin Johnneylee Jack Rollins Magnus Bergmark trogdoro Wilker Lúcio
Willy Blandin

Hi Craig,

I watched your RubyConf talk yesterday and discovered Xiki.
Being a Vim user, I decided to help a bit with the integration.

What I did

  • Gathered and improved small fixes from #36 and #19
  • Nested commands (going up the tree)
  • Expand and collapse
  • Got rid of vim/line (indirection didn't seem to be justified) and replaced vim/tree
  • Provided tests for most of this
  • (NOT ANYMORE, see below) Added some more test infrastructure (minitest, guard to rerun test automatically), I think it is good to enforce good practices at the project level, but do whatever you want with these!
  • Updated vim status

What I did not do

  • Filtering on additional key strokes, not sure how to approach this in Vim (but didn't look at your emacs implementation)

Comments
I strongly believe Xiki is the right way to interact with a computer, at least from a keyboard. I have always been a huge fan of CLI interfaces and seeing projects like yours is refreshing, but...

  1. I find the shelling-out option too slow compared to the snappiness of your emacs demo.
    A TCP client/server model seems like the right thing to do, with most of the code being editor-agnostic with only thin editor-specific presentation layers.

  2. I had trouble dealing with the codebase and gave up on reusing existing data structure after 15min of browsing. That's why I added XikiVim::Tree. Get rid of the $el already :)

Is there anything planned to address these issues?

Anyway, thank you and keep up the good work! :)

Johnneylee Jack Rollins

Would it be possible for you to break out the Gemfile changes into a different commit so they can be either accepted or not as a separate commit please?

Willy Blandin

I agree it was bold of me to add a dependency.
I removed the changes to Gemfile and removed the Guardfile.
I'll add them back if requested.

@trogdoro care to weigh in?

Magnus Bergmark

Eagerly awaiting this. :-)

trogdoro
Owner
Wilker Lúcio

any news on this? really waiting for this merge :)

trogdoro
Owner
trogdoro
Owner

Thanks for the contributions!

Got rid of vim/line (indirection didn't seem to be justified) and replaced vim/tree.

I definitely intend for the line.rb class to exist, so this pull request would be awkward to merge, since I'd have to restore it back after the merge. There isn't much in line.rb now, but in the emacs version there is a lot. Will be tough to maintain in the future if the vim classes are named/structured differently than the emacs ones.

Willy Blandin

Hi Craig,

To be honest, I too think that this pull request doesn't fit in the codebase anymore.
As I said before, I tried to reuse existing classes, but $el was everywhere so it was quite hard.

For instance, if the Line class is supposed to be generic, then it should be a superclass of EmacsLine and VimLine.
I'm glad to hear that a refactoring is in progress though, because it should be easy to redo what I did (not much really) afterwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Nov 28, 2012
Willy Blandin blandinw Better Vim integration 2038836
This page is out of date. Refresh to see the latest.
4 .gitignore
@@ -17,3 +17,7 @@ tmp
17 17 .yardoc
18 18 _yardoc
19 19 doc/
  20 +
  21 +# vim
  22 +*.swp
  23 +*.swo
11 etc/vim/vim_status.notes
@@ -10,22 +10,21 @@
10 10 > If your vim version doesn't have ruby enabled
11 11 The standard macvim download has the ruby. Compile vim with the --enable-rubyinterp flag installed.
12 12
13   -
14 13 > What's implemented
15   -Just double-clicking on one line and shelling out to 'xiki'
  14 +- Double-clicking on one line and shelling out to 'xiki'
16 15 and inserting the results
17   -
18   -
19   -> What needs to be implemented
20 16 - Making this work for nested paths, like:
21 17 docs/
22 18 - faq/
23 19 - It needs to climb the path and pass it in to the command
24 20 - Making it collapse when the line underneath it is indented lower
  21 +
  22 +> What needs to be done
  23 +- Write documentation (doc/)
  24 +- Check for Ruby and Vim versions
25 25 - Making it incrementally search after commands are inserted
26 26 - is there a command in vim to read 1 char from the user?
27 27
28   -
29 28 > Help out
30 29 Join the xiki google group if you want to help implement this.
31 30
44 etc/vim/xiki.vim
... ... @@ -1,16 +1,34 @@
  1 +if exists("loaded_xikivim")
  2 + finish
  3 +endif
  4 +" if v:version < 700
  5 +" echoerr "XikiVim: this plugin requires vim >= 7!"
  6 +" finish
  7 +" endif
  8 +let loaded_xikivim = 1
  9 +
1 10 function! XikiLaunch()
2   - ruby << EOF
3   - xiki_dir = ENV['XIKI_DIR']
4   - ['ol', 'vim/line', 'vim/tree'].each {|o| require "#{xiki_dir}/lib/xiki/#{o}"}
5   - line = Line.value
6   - indent = line[/^ +/]
7   - command = "xiki #{line}"
8   - result = `#{command}`
9   - Tree << result
10   -EOF
  11 + ruby << eoruby
  12 + # $".clear # dev
  13 + $XIKI_DIR ||= ENV['XIKI_DIR'] || %x,xiki directory,.strip
  14 +
  15 + begin
  16 + require "#{$XIKI_DIR}/lib/xiki/vim.rb"
  17 + rescue LoadError
  18 + Vim.message("Couldn't find Xiki home!")
  19 + raise
  20 + end
  21 +
  22 + path = XikiVim::construct_path $curbuf
  23 + XikiVim::take_action $curbuf, path
  24 +eoruby
11 25 endfunction
  26 +command! XikiLaunch :call XikiLaunch()
  27 +
  28 +if !exists("xikivim_no_mappings")
  29 + inoremap <silent> <2-LeftMouse> <C-c>:XikiLaunch<CR>a
  30 + nnoremap <silent> <2-LeftMouse> <C-c>:XikiLaunch<CR>i
  31 + inoremap <silent> <Leader><Leader> <C-c>:XikiLaunch<CR>a
  32 + nnoremap <silent> <Leader><Leader> :XikiLaunch<CR>
  33 +endif
12 34
13   -nmap <silent> <2-LeftMouse> :call XikiLaunch()<CR>
14   -imap <silent> <2-LeftMouse> <C-c>:call XikiLaunch()<CR>i
15   -imap <silent> <C-CR> <C-c>:call XikiLaunch()<CR>i
16   -nmap <silent> <C-CR> :call XikiLaunch()<CR>
80 lib/xiki/vim.rb
... ... @@ -0,0 +1,80 @@
  1 +module XikiVim
  2 + INDENT = ' ' * 2
  3 + INDENT_RE = /^(\s*)/
  4 +end
  5 +
  6 +%w{ vim/tree }.each do |lib|
  7 + require File.expand_path("../#{lib}", __FILE__)
  8 +end
  9 +
  10 +module XikiVim
  11 + # go up until there is no whitespace
  12 + # returns built path + absolute row of root
  13 + def self.construct_path buffer
  14 + path = ""
  15 + offset = 0
  16 + line = buffer.line_number
  17 + cur_id_lvl = 1e10
  18 +
  19 + # build path as long as line starts with whitespaces
  20 + until (match = buffer[line + offset].match(/^(\s+)/)).nil?
  21 + line_id_lvl = match[1].length
  22 +
  23 + # check if we reached a parent, and append it to path
  24 + if line_id_lvl < cur_id_lvl
  25 + path = sanitize(buffer[line + offset]) + path
  26 + cur_id_lvl = line_id_lvl
  27 + end
  28 + offset -= 1
  29 + end
  30 + path = sanitize(buffer[line + offset]) + "/" + path
  31 +
  32 + return path
  33 + end
  34 +
  35 + # FIXME error handling on shell out
  36 + def self.take_action buffer, path
  37 + ensure_format buffer
  38 +
  39 + if expanded? buffer.line
  40 + buffer[buffer.line_number] = buffer.line.gsub(/-/, '+')
  41 + block(buffer, true)
  42 + else
  43 + # check cache
  44 + buffer[buffer.line_number] = buffer.line.gsub(/\+/, '-')
  45 + xiki_resp = %x{ xiki "#{path}" }
  46 + t = Tree.new xiki_resp
  47 + t.render buffer
  48 + end
  49 + end
  50 +
  51 + protected
  52 + def self.expanded? line
  53 + line.lstrip[0].chr == '-'
  54 + end
  55 +
  56 + def self.sanitize line
  57 + line.strip.gsub(/^\+\s*|^-\s*/, '')
  58 + end
  59 +
  60 + # get the sub-block (indentation-wise), optionally delete it
  61 + def self.block vimbuffer, do_delete
  62 + id_level = vimbuffer.line[INDENT_RE, 1]
  63 + buffer = ""
  64 + i = vimbuffer.line_number + 1
  65 +
  66 + while id_level < vimbuffer[i][INDENT_RE, 1]
  67 + buffer << vimbuffer[i]
  68 + do_delete ? vimbuffer.delete(i) : i += 1
  69 + end
  70 +
  71 + return buffer
  72 + end
  73 +
  74 + def self.ensure_format buffer
  75 + char = buffer.line.lstrip[0].chr
  76 + if char != '-' && char != '+'
  77 + buffer.line = '+ ' + buffer.line
  78 + end
  79 + end
  80 +end
8 lib/xiki/vim/line.rb
... ... @@ -1,8 +0,0 @@
1   -class Line
2   - def self.number
3   - $curbuf.line_number
4   - end
5   - def self.value
6   - $curbuf.line
7   - end
8   -end
80 lib/xiki/vim/tree.rb
... ... @@ -1,9 +1,77 @@
1   -class Tree
2   - def self.<< txt
3   - line_number, line = Line.number, Line.value
4   - indent = line[/^ +/]
5   - txt.split("\n").each_with_index do |line, i|
6   - $curbuf.append(line_number + i, "#{indent} #{line}")
  1 +module XikiVim
  2 + class Tree
  3 + attr_accessor :value, :children
  4 +
  5 + def initialize txt, consume_root = false
  6 + lines = txt.split("\n")
  7 +
  8 + if consume_root
  9 + @value = lines.shift || ""
  10 + end
  11 +
  12 + @children = if lines.empty?
  13 + []
  14 + else
  15 + parse_result lines
  16 + end
  17 + end
  18 +
  19 + # returns a list of children given a txt, using indentation level
  20 + def parse_result lines
  21 + children = []
  22 +
  23 + # 1. from blob to string array
  24 + id_lvl = lines.first[INDENT_RE, 1].length
  25 + buffer = ""
  26 + lines.each do |line|
  27 + id = line[INDENT_RE, 1].length
  28 +
  29 + # not direct children, buffer
  30 + if id > id_lvl
  31 + buffer += line + "\n"
  32 + next
  33 + end
  34 +
  35 + # back to direct children id level, done buffering
  36 + # add buffered children to their parent (last children on stack)
  37 + if not buffer.empty?
  38 + children.last << "\n" << buffer
  39 + buffer = ""
  40 + end
  41 +
  42 + children << line
  43 + end
  44 + # retrieve being parsed children
  45 + if not buffer.empty?
  46 + children.last << "\n" << buffer
  47 + buffer = ""
  48 + end
  49 +
  50 + # 2. from string array to trees
  51 + children.map! do |line|
  52 + Tree.new line, true # create a new tree, consuming root
  53 + end
  54 +
  55 + children
  56 + end
  57 +
  58 + def render buffer, indent = nil, i = 0
  59 + if indent.nil?
  60 + indent = buffer.line.nil?? "" : buffer.line[INDENT_RE, 1]
  61 + end
  62 +
  63 + if not @value.nil?
  64 + buffer.append buffer.line_number + i, indent + @value
  65 + i += 1
  66 + end
  67 +
  68 + indent += INDENT
  69 + @children.each do |c|
  70 + i = c.render buffer, indent, i
  71 + i += 1
  72 + end
  73 +
  74 + return i - 1
7 75 end
8 76 end
9 77 end
6 tests/test_helpers.rb
... ... @@ -0,0 +1,6 @@
  1 +def lib
  2 + File.expand_path '../../lib', __FILE__
  3 +end
  4 +
  5 +$:.unshift lib
  6 +
33 tests/vim/helpers.rb
... ... @@ -0,0 +1,33 @@
  1 +require File.expand_path('../../test_helpers.rb', __FILE__)
  2 +require 'minitest/autorun'
  3 +require 'xiki/vim'
  4 +
  5 +class FakeBuffer
  6 + attr_accessor :line_number
  7 +
  8 + def initialize lines
  9 + @lines = lines
  10 + @line_number = 0
  11 + end
  12 +
  13 + def [] ln
  14 + @lines[ln]
  15 + end
  16 +
  17 + def append ln, l
  18 + @lines.insert ln, l
  19 + end
  20 +
  21 + def line
  22 + @lines[@line_number]
  23 + end
  24 +
  25 + def line= l
  26 + @lines[@line_number] = l
  27 + end
  28 +
  29 + def dump
  30 + @lines.join("\n")
  31 + end
  32 +end
  33 +
52 tests/vim/tree_test.rb
... ... @@ -0,0 +1,52 @@
  1 +require File.expand_path('../helpers.rb', __FILE__)
  2 +require 'xiki/vim'
  3 +
  4 +class String
  5 + def n
  6 + self.gsub(/^\s*/, '').strip
  7 + end
  8 +end
  9 +
  10 +module XikiVim
  11 + class TestTree < MiniTest::Unit::TestCase
  12 + def test_parse
  13 + docs_faq = <<eodocs
  14 +docs/faq
  15 + + What is Xiki/
  16 + + What does Xiki stand for/
  17 + + What are Xiki's main dependencies/
  18 + + What's the best way to figure out how to do something/
  19 + + How can I keep up to date with the latest Xiki happenings/
  20 +eodocs
  21 +
  22 + what_xiki = <<eoxiki
  23 +Xiki is an environment that can be used in several ways:
  24 +
  25 +- Shell terminal on steroids
  26 +- Development environment for coding rails or node.js apps, etc.
  27 +- Framework for making lightweight user interfaces
  28 +eoxiki
  29 +
  30 + empty = Tree.new ""
  31 + assert_equal 0, empty.children.length
  32 +
  33 + iptree = Tree.new "192.168.0.14"
  34 + assert_equal 1, iptree.children.length
  35 +
  36 + docstree = Tree.new docs_faq
  37 + assert_equal 1, docstree.children.length
  38 + assert_equal 5, docstree.children[0].children.length
  39 +
  40 + buffer = FakeBuffer.new []
  41 + docstree.render(buffer)
  42 + assert_equal docs_faq.n, buffer.dump.n
  43 +
  44 + buffer = FakeBuffer.new []
  45 + xikitree = Tree.new what_xiki
  46 + xikitree.render(buffer)
  47 + assert_equal what_xiki.n, buffer.dump.n
  48 + end
  49 + end
  50 +end
  51 +
  52 +# vim: nowrap
26 tests/vim/vim_test.rb
... ... @@ -0,0 +1,26 @@
  1 +require File.expand_path('../helpers.rb', __FILE__)
  2 +
  3 +class TestVim < MiniTest::Unit::TestCase
  4 + def test_construct_path
  5 + ary = ['docs/faq', ' + What is Xiki/', ' + What does Xiki stand for/']
  6 + buffer = FakeBuffer.new ary
  7 + buffer.line_number = 1
  8 + assert_equal "docs/faq/What is Xiki/", XikiVim.construct_path(buffer)
  9 +
  10 + ary = ['docs/faq']
  11 + buffer = FakeBuffer.new ary
  12 + assert_equal "docs/faq/", XikiVim::construct_path(buffer)
  13 +
  14 + ary = ['ls ']
  15 + buffer = FakeBuffer.new ary
  16 + assert_equal "ls/", XikiVim::construct_path(buffer)
  17 + end
  18 +
  19 + def test_ensure_format
  20 + buffer = FakeBuffer.new ["docs"]
  21 + XikiVim::ensure_format buffer
  22 + assert_equal "+ docs", buffer.dump
  23 + end
  24 +end
  25 +
  26 +# vim: nowrap

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.