Class: Appear::Editor::TmuxIde
- Inherits:
-
Service
- Object
- BaseService
- Service
- Appear::Editor::TmuxIde
- Defined in:
- lib/appear/editor.rb
Overview
TmuxIde is an editor that treasts a collection of Tmux splits holding an Nvim process as an IDE. A “session” is a Tmux window that at least contains an Nvim instance, although new sessions are split like this:
| | | nvim | | | |———–| |$ |$ | | | | |———–|
Instance Method Summary collapse
-
#call(*filenames) ⇒ Object
reveal files in an existing or new IDE session.
-
#create_ide(filename) ⇒ Object
Create a new IDE instance editing ‘filename`.
-
#find_nvim_for_file(filename) ⇒ ::Appear::Editor::Nvim?
Find the appropriate Nvim session for a given filename.
-
#find_or_create_ide(filename) ⇒ Object
Find or create an IDE, then open this file in it.
-
#find_tmux_pane(nvim) ⇒ Appear::Tmux::Pane?
find the tmux pane holding an nvim editor instance.
-
#initialize(svcs = {}) ⇒ TmuxIde
constructor
A new instance of TmuxIde.
-
#path_contains?(parent, child) ⇒ Boolean
Check if a child path is contained by a parent path.
-
#project_root(filename) ⇒ String
Guess the project root for a given path by inspecting its parent directories for certain markers like git roots.
- #wait_until(max_duration, sleep = 0.1) ⇒ Object
Methods inherited from BaseService
delegate, require_service, required_services
Constructor Details
Instance Method Details
#call(*filenames) ⇒ Object
reveal files in an existing or new IDE session
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 |
# File 'lib/appear/editor.rb', line 173 def call(*filenames) nvims = [] nvim_to_session = {} filenames.each do |filename| filename = File.(filename) nvim, pane = find_or_create_ide(filename) # focuses the file in the nvim instance, or start editing it. Thread.new { nvim.drop(filename) } nvims << nvim unless nvims.include?(nvim) nvim_to_session[nvim] = pane.session end nvims.map do |nvim| Thread.new do # go ahead and reveal our nvim next true if services.revealer.call(nvim.pid) session = nvim_to_session[nvim] # if we didn't return, we need to create a Tmux client for our # session. command = services.tmux.attach_session_command(session) terminal = services.terminals.get term_pane = terminal.new_window(command.to_s) terminal.reveal_pane(term_pane) end end.each(&:join) log "#{self.class}: finito." end |
#create_ide(filename) ⇒ Object
Create a new IDE instance editing ‘filename`
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 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/appear/editor.rb', line 110 def create_ide(filename) dir = project_root(filename) # find or create session tmux_session = services.tmux.sessions.sort_by { |s| s.windows.length }.last tmux_session ||= services.tmux.new_session( # -c: current directory :c => dir ) # find or create window window = tmux_session.windows.find do |win| win.panes.first.current_path == dir end window ||= tmux_session.new_window( # -c: current directory :c => dir, # -d: do not focus :d => true, ) # remember our pid list existing_nvims = services.processes.pgrep(Nvim::NEOVIM) # split window across the middle, into a big and little pane main = window.panes.first main.send_keys([Nvim.edit_command(filename).to_s, "\n"], :l => true) left = main.split(:p => 30, :v => true, :c => dir) # cut the smaller bottom pane in half right = left.split(:p => 50, :h => true, :c => dir) # put a vim in the top pane, and select it #[left, right].each_with_index do |pane, idx| #pane.send_keys(["bottom pane ##{idx}"], :l => true) #end # Hacky way to wait for nvim to launch! This should take at most 2 # seconds, otherwise your vim is launching too slowley ;) wait_until(2) { (services.processes.pgrep(Nvim::NEOVIM) - existing_nvims).length >= 1 } nvim = find_nvim_for_file(filename) return nvim, find_tmux_pane(nvim) end |
#find_nvim_for_file(filename) ⇒ ::Appear::Editor::Nvim?
Find the appropriate Nvim session for a given filename. First, we try to find a session actually editing this file. If none exists, we find the session with the deepest CWD that contains the filename.
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/appear/editor.rb', line 61 def find_nvim_for_file(filename) update_nvims cwd_to_nvim = {} @nvims.each do |_, nvim| return nvim if nvim.find_buffer(filename) end match = @cwd_by_depth.find { |cwd| path_contains?(cwd, filename) } return nil unless match @cwd_to_nvim[match] end |
#find_or_create_ide(filename) ⇒ Object
Find or create an IDE, then open this file in it.
101 102 103 104 105 |
# File 'lib/appear/editor.rb', line 101 def find_or_create_ide(filename) nvim = find_nvim_for_file(filename) return nvim, find_tmux_pane(nvim) unless nvim.nil? create_ide(filename) end |
#find_tmux_pane(nvim) ⇒ Appear::Tmux::Pane?
find the tmux pane holding an nvim editor instance.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/appear/editor.rb', line 78 def find_tmux_pane(nvim) @tmux_memo.call(nvim) do tree = services.processes.process_tree(nvim.pid) tmux_server = tree.find { |p| p.name == 'tmux' } next nil unless tmux_server # the first join should be the tmux pane holding our # nvim session. proc_and_panes = Util::Join.join(:pid, services.tmux.panes, tree) pane_join = proc_and_panes.first next nil unless pane_join # new method on join: let's you get an underlying # object out of the join if it matches a predicate. next pane_join.unjoin do |o| o.is_a? ::Appear::Tmux::Pane end end end |
#path_contains?(parent, child) ⇒ Boolean
Check if a child path is contained by a parent path. as dumb as they come TODO: use a real path_contains algorithm.
50 51 52 53 |
# File 'lib/appear/editor.rb', line 50 def path_contains?(parent, child) p, c = Pathname.new(parent), Pathname.new(child) c..to_s.start_with?(p..to_s) end |
#project_root(filename) ⇒ String
Guess the project root for a given path by inspecting its parent directories for certain markers like git roots.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/appear/editor.rb', line 209 def project_root(filename) # TODO: a real constant? Some internet-provided list? # these are files that indicate the root of a project markers = %w(.git .hg Gemfile package.json setup.py README README.md) p = Pathname.new(filename). p.ascend do |path| is_root = markers.any? do |marker| path.join(marker).exist? end return path if is_root end # no markers were found return p.to_s if p.directory? return p.dirname.to_s end |
#wait_until(max_duration, sleep = 0.1) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/appear/editor.rb', line 154 def wait_until(max_duration, sleep = 0.1) raise ArgumentError.new("no block given") unless block_given? start = Time.new limit = start + max_duration iters = 0 while Time.new < limit if yield log("wait_until(max_duration=#{max_duration}, sleep=#{sleep}) slept #{iters} times, took #{Time.new - start}s") return true end iters = iters + 1 sleep(sleep) end false end |