appear 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.doc-coverage +1 -1
- data/CHANGELOG.md +11 -0
- data/README.md +1 -1
- data/bin/appear +6 -0
- data/lib/appear/command.rb +28 -0
- data/lib/appear/config.rb +7 -0
- data/lib/appear/constants.rb +1 -4
- data/lib/appear/editor/nvim.rb +251 -0
- data/lib/appear/editor.rb +262 -0
- data/lib/appear/instance.rb +4 -0
- data/lib/appear/lsof.rb +84 -51
- data/lib/appear/mac_os.rb +12 -2
- data/lib/appear/output.rb +16 -0
- data/lib/appear/processes.rb +4 -6
- data/lib/appear/revealers.rb +65 -62
- data/lib/appear/runner.rb +25 -5
- data/lib/appear/terminal.rb +127 -0
- data/lib/appear/tmux.rb +285 -39
- data/lib/appear/util/command_builder.rb +148 -0
- data/lib/appear/util/join.rb +144 -0
- data/lib/appear/util/memoizer.rb +83 -0
- data/lib/appear/util/value_class.rb +57 -0
- data/lib/appear/util.rb +6 -0
- data/lib/appear.rb +55 -1
- data/scripts/console +9 -1
- data/tools/macOS-helper.js +24 -16
- data/tools/unix-dropper.applescript +167 -0
- metadata +11 -3
- data/lib/appear/join.rb +0 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67b9aebb07fd1277e44209ecc142ce4266b2a614
|
4
|
+
data.tar.gz: e234f31f3e772957cf50967eae4ceb633ee1951c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0caad8654c99c2d4b98fe759fb37ad8919443ce332d638af1459494f3614887336ecaf38550ba733bd4a85558c0672e479e2762d16dd1e0619c6b70d1d5ee2d7
|
7
|
+
data.tar.gz: 3c98564f230a8a7271920b122d583628155411fa76c0e62b2a49efc9fa677b36b8c49f9ba4dcb30f1a2e6f75f65ed372ec08a7ee4521fa0ec87ef55d003274e0
|
data/.doc-coverage
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
100.0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.2.0
|
4
|
+
|
5
|
+
- new experimental feature: `appear --edit nvim FILES...` to edit files in Nvim inside Tmux
|
6
|
+
inside iTerm2. Support is limited.
|
7
|
+
- refactors of many core components:
|
8
|
+
- Tmux service
|
9
|
+
- MacRevealers were split into Terminal services
|
10
|
+
- new handy Utils classes in appear/util, including the very handy
|
11
|
+
`Appear::Util::CommandBuilder`, and the slightly less useful
|
12
|
+
`Appear::Util::Memoizer`.
|
13
|
+
|
3
14
|
## 1.1.1
|
4
15
|
|
5
16
|
- passing in a PID was broken.
|
data/README.md
CHANGED
data/bin/appear
CHANGED
data/lib/appear/command.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'appear/constants'
|
2
2
|
require 'appear/config'
|
3
3
|
require 'appear/instance'
|
4
|
+
require 'appear/editor'
|
4
5
|
require 'optparse'
|
5
6
|
|
6
7
|
module Appear
|
@@ -34,6 +35,11 @@ module Appear
|
|
34
35
|
@config.record_runs = flag
|
35
36
|
end
|
36
37
|
|
38
|
+
o.on('-e', '--edit [EDITOR]', "instead of revealing a PID, edit a file the given editor. Editors: #{::Appear::Editor::ALL.map {|c| c.name}}") do |flag|
|
39
|
+
@config.edit_file = true
|
40
|
+
@config.editor = flag
|
41
|
+
end
|
42
|
+
|
37
43
|
o.on('--version', 'show version information, then exit') do
|
38
44
|
puts "appear #{Appear::VERSION}"
|
39
45
|
puts " author: Jake Teton-Landis"
|
@@ -60,6 +66,16 @@ module Appear
|
|
60
66
|
def execute(all_args)
|
61
67
|
argv = option_parser.parse(*all_args)
|
62
68
|
|
69
|
+
if @config.edit_file
|
70
|
+
return execute_edit(argv)
|
71
|
+
else
|
72
|
+
return execute_pid(argv)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def execute_pid(argv)
|
63
79
|
if argv.empty?
|
64
80
|
pid = Process.pid
|
65
81
|
start_message = "STARTING. pid: #{pid} (current process pid)"
|
@@ -83,5 +99,17 @@ module Appear
|
|
83
99
|
exit 2
|
84
100
|
end
|
85
101
|
end
|
102
|
+
|
103
|
+
def execute_edit(argv)
|
104
|
+
raise ArgumentError.new("no file passed") if argv.empty?
|
105
|
+
|
106
|
+
revealer = Appear::Instance.new(@config)
|
107
|
+
# this is a sin, for now
|
108
|
+
# TODO: remove instance_variable_get, use a real API
|
109
|
+
services = revealer.instance_variable_get('@all_services')
|
110
|
+
ide = ::Appear::Editor::TmuxIde.new(services)
|
111
|
+
ide.call(*argv)
|
112
|
+
end
|
113
|
+
|
86
114
|
end
|
87
115
|
end
|
data/lib/appear/config.rb
CHANGED
@@ -15,10 +15,17 @@ module Appear
|
|
15
15
|
# @return [Boolean] default false
|
16
16
|
attr_accessor :record_runs
|
17
17
|
|
18
|
+
# @return [Boolean] default false
|
19
|
+
attr_accessor :edit_file
|
20
|
+
|
21
|
+
# @return [String, nil] default nil
|
22
|
+
attr_accessor :editor
|
23
|
+
|
18
24
|
# sets defaults
|
19
25
|
def initialize
|
20
26
|
self.silent = true
|
21
27
|
self.record_runs = false
|
28
|
+
self.edit_file = false
|
22
29
|
end
|
23
30
|
end
|
24
31
|
end
|
data/lib/appear/constants.rb
CHANGED
@@ -2,14 +2,11 @@ require 'pathname'
|
|
2
2
|
|
3
3
|
module Appear
|
4
4
|
# the version of Appear
|
5
|
-
VERSION = '1.
|
5
|
+
VERSION = '1.2.0'
|
6
6
|
|
7
7
|
# root error for our library; all other errors inherit from this one.
|
8
8
|
class Error < StandardError; end
|
9
9
|
|
10
|
-
# we also put common constants in here because it's a good spot.
|
11
|
-
# Should we rename this file to 'appear/constants' ?
|
12
|
-
|
13
10
|
# the root of the Appear project directory
|
14
11
|
MODULE_DIR = Pathname.new(__FILE__).realpath.join('../../..')
|
15
12
|
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'appear/util/command_builder'
|
2
|
+
require 'appear/util/value_class'
|
3
|
+
require 'yaml'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Appear
|
7
|
+
module Editor
|
8
|
+
# Raised if we have problems interacting with Nvim.
|
9
|
+
class NvimError < ::Appear::Error
|
10
|
+
attr_reader :from_err
|
11
|
+
# @param msg [String]
|
12
|
+
# @param from_err [Exception] pass in another error to wrap it in an
|
13
|
+
# NvimError.
|
14
|
+
def initialize(msg, from_err = nil)
|
15
|
+
super(msg)
|
16
|
+
@from_err = from_err
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wraps nvim-remote to implement basic nvim support.
|
21
|
+
# @see https://github.com/mhinz/neovim-remote
|
22
|
+
#
|
23
|
+
# I put this in my zshrc:
|
24
|
+
# `export NVIM_LISTEN_ADDRESS="$HOME/.vim/sockets/vim-zsh-$$.sock"`
|
25
|
+
# this opens a seperate nvim socket for each new Zsh shell. You'll still
|
26
|
+
# get nvims trying to open the same socket if you use ctrl-z and fg and
|
27
|
+
# stuff to manage 'em, but overall this solves needing command a bunch of
|
28
|
+
# different things.
|
29
|
+
class Nvim < Service
|
30
|
+
# the `neovim-remote` command name
|
31
|
+
NVR = 'nvr'.freeze
|
32
|
+
# the `nvim` command name
|
33
|
+
NEOVIM = 'nvim'.freeze
|
34
|
+
|
35
|
+
# the value to use for Vim buffers with no name. This is the UI value
|
36
|
+
# that Vim usually shows.
|
37
|
+
NO_NAME = "[No Name]".freeze
|
38
|
+
|
39
|
+
# value class describing a vim pane.
|
40
|
+
class Pane < Util::ValueClass
|
41
|
+
# @return [Fixnum] vim tab number
|
42
|
+
property :tab
|
43
|
+
|
44
|
+
# @return [Fixnum] vim window number
|
45
|
+
property :window
|
46
|
+
|
47
|
+
# @return [Fixnum] vim buffer number
|
48
|
+
property :buffer
|
49
|
+
|
50
|
+
# @return [Hash<Symbol, String>] data about the buffer
|
51
|
+
property :buffer_info
|
52
|
+
end
|
53
|
+
|
54
|
+
delegate :run, :runner
|
55
|
+
|
56
|
+
# This constant maps logical name to a string of filename-modifiers, for
|
57
|
+
# the vim fnamemodify() function. When we collect information about
|
58
|
+
# buffers, we get each of these expansions applied to the buffer name.
|
59
|
+
#
|
60
|
+
# This mapping was copied from the BufExplorer vim plugin.
|
61
|
+
BUFFER_FILENAME_EXPANSIONS = {
|
62
|
+
:name => '',
|
63
|
+
:absolute_path => ':p',
|
64
|
+
:dirname => ':p:h',
|
65
|
+
:relative_path => ':~:.',
|
66
|
+
:relative_dirname => ':~:.:h',
|
67
|
+
:shortname => ':t'
|
68
|
+
}.freeze
|
69
|
+
# order in which we pass these to vim.
|
70
|
+
BUFFER_FILENAME_ORDER = BUFFER_FILENAME_EXPANSIONS.keys.freeze
|
71
|
+
|
72
|
+
# @return [#to_s] path to the unix socket used to talk to Nvim
|
73
|
+
attr_reader :socket
|
74
|
+
|
75
|
+
|
76
|
+
# List all the sockets found in ~/.vim/sockets.
|
77
|
+
# I put this in my zshrc to make this work:
|
78
|
+
# `export NVIM_LISTEN_ADDRESS="$HOME/.vim/sockets/vim-zsh-$$.sock"`
|
79
|
+
# @return [Array<Pathname>]
|
80
|
+
def self.sockets
|
81
|
+
Dir.glob(File.expand_path('~/.vim/sockets/*.sock'))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Spawn a new NVIM instance, then connect to its socket.
|
85
|
+
def self.edit_command(filename)
|
86
|
+
::Appear::Util::CommandBuilder.new(NEOVIM).args(filename)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param socket [#to_s] UNIX socket to use to talk to Nvim.
|
90
|
+
def initialize(socket, svc = {})
|
91
|
+
super(svc)
|
92
|
+
@socket = socket
|
93
|
+
@expr_memo = ::Appear::Util::Memoizer.new
|
94
|
+
end
|
95
|
+
|
96
|
+
# evaluate a Vimscript expression
|
97
|
+
#
|
98
|
+
# @param vimstring [String] the expression, eg "fnamemodify('~', ':p')"
|
99
|
+
# @return [Object] expression result, parsed as YAML.
|
100
|
+
def expr(vimstring)
|
101
|
+
@expr_memo.call(vimstring) do
|
102
|
+
parse_output_as_yaml(run(command.flag('remote-expr', vimstring).to_a))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Perform a Vim command
|
107
|
+
#
|
108
|
+
# @param vimstring [String] the command, eg ":buffers"
|
109
|
+
def cmd(vimstring)
|
110
|
+
run(command.flag('c', vimstring).to_a)
|
111
|
+
end
|
112
|
+
|
113
|
+
# PID of the remote vim session
|
114
|
+
#
|
115
|
+
# @return [Fixnum]
|
116
|
+
def pid
|
117
|
+
expr('getpid()')
|
118
|
+
end
|
119
|
+
|
120
|
+
# Working directory of the remote vim session
|
121
|
+
#
|
122
|
+
# @return [Pathname]
|
123
|
+
def cwd
|
124
|
+
expr('getcwd()')
|
125
|
+
end
|
126
|
+
|
127
|
+
# Get all the Vim panes in all tabs.
|
128
|
+
#
|
129
|
+
# @return [Array<Pane>] data
|
130
|
+
def panes
|
131
|
+
@expr_memo.call(nil, 'panes') do
|
132
|
+
all_buffers = get_buffers
|
133
|
+
all_panes = []
|
134
|
+
get_windows.each_with_index do |wins, tab_idx|
|
135
|
+
wins.each_with_index do |buffer, win_idx|
|
136
|
+
all_panes << Pane.new(
|
137
|
+
# in Vim, tabs and windows start indexing at 1
|
138
|
+
:tab => tab_idx + 1,
|
139
|
+
:window => win_idx + 1,
|
140
|
+
:buffer => buffer,
|
141
|
+
:buffer_info => all_buffers[buffer - 1]
|
142
|
+
)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
all_panes
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Find the buffer for a given filename.
|
150
|
+
#
|
151
|
+
# @param filename [String]
|
152
|
+
# @return [Hash, nil] buffer info, nil if not found
|
153
|
+
def find_buffer(filename)
|
154
|
+
p = File.expand_path(filename)
|
155
|
+
get_buffers.find do |buffer|
|
156
|
+
buffer[:absolute_path] == p
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Find the pane for a given filename.
|
161
|
+
#
|
162
|
+
# @param filename [String]
|
163
|
+
# @return [Pane, nil] nil if not found
|
164
|
+
def find_pane(filename)
|
165
|
+
p = Pathname.new(filename).expand_path.to_s
|
166
|
+
panes.find do |pane|
|
167
|
+
buff = pane.buffer_info
|
168
|
+
buff[:absolute_path] == p
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Open a file in vim, or focus a window editing this file, if it's
|
173
|
+
# already open. Vim has a command a lot like this, but I can't get it to
|
174
|
+
# work in either of [vim,nvim] the way the docs say it should. That's why
|
175
|
+
# it's re-implemented here.
|
176
|
+
#
|
177
|
+
# @param filename [String]
|
178
|
+
def drop(filename)
|
179
|
+
# not using find_buffer because we care about panes; we'll have to open
|
180
|
+
# a new one if one doesn't exist anyways.
|
181
|
+
pane = find_pane(filename)
|
182
|
+
|
183
|
+
if pane
|
184
|
+
reveal_pane(pane)
|
185
|
+
return pane
|
186
|
+
end
|
187
|
+
|
188
|
+
# TODO: consider options other than vsplit
|
189
|
+
# TODO: such as opening in the main window with :edit if the main
|
190
|
+
# window is an empty [NoName] buffer, from a new vim maybe?
|
191
|
+
cmd("vertical split #{filename.shellescape}")
|
192
|
+
find_pane(filename)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Reveal a pane in Nvim
|
196
|
+
#
|
197
|
+
# @param pane [Pane]
|
198
|
+
def reveal_pane(pane)
|
199
|
+
# go to tab
|
200
|
+
cmd("tabnext #{pane.tab}")
|
201
|
+
# go to window
|
202
|
+
cmd("#{pane.window} . wincmd w")
|
203
|
+
end
|
204
|
+
|
205
|
+
# Description of this Neovim socket and session.
|
206
|
+
# @return [String]
|
207
|
+
def to_s
|
208
|
+
"#<#{self.class.name}:0x#{'%x' % (object_id << 1)} in #{cwd.inspect}>"
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def command
|
214
|
+
::Appear::Util::CommandBuilder.new(NVR).flags(:servername => @socket.to_s)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Vimscript return values look vaguely like YAML, so parse them with
|
218
|
+
# YAML.
|
219
|
+
# @param output [String]
|
220
|
+
def parse_output_as_yaml(output)
|
221
|
+
begin
|
222
|
+
return YAML.load(output)
|
223
|
+
rescue => err
|
224
|
+
raise NvimError.new(output, err)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def get_windows
|
229
|
+
expr(%Q[map( range(1, tabpagenr('$')), "tabpagebuflist(v:val)" )])
|
230
|
+
end
|
231
|
+
|
232
|
+
def get_buffers
|
233
|
+
cmd = BUFFER_FILENAME_ORDER.map do |type|
|
234
|
+
"fnamemodify(bufname(v:val), '#{BUFFER_FILENAME_EXPANSIONS[type]}')"
|
235
|
+
end.join(', ')
|
236
|
+
|
237
|
+
as_a = expr(%Q(map( range(1, bufnr('$')), "[v:val, #{cmd} ]" )))
|
238
|
+
as_a.map do |row|
|
239
|
+
row = row.dup
|
240
|
+
buf = {:buffer => row.shift}
|
241
|
+
row.each_with_index do |it, i|
|
242
|
+
buf[BUFFER_FILENAME_ORDER[i]] = it
|
243
|
+
end
|
244
|
+
buf[:name] = NO_NAME if buf[:name].empty?
|
245
|
+
buf
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require 'appear/util/command_builder'
|
2
|
+
require 'appear/util/join'
|
3
|
+
|
4
|
+
module Appear
|
5
|
+
# Appear::Editor is a sub-library of Appear, for appearing files, instead of
|
6
|
+
# processes. Appear::Editor's job is to open a given file in an existing
|
7
|
+
# editor session, or start a new session editing that file.
|
8
|
+
#
|
9
|
+
# Appear::Editor should feature drivers for many editors in the future, but
|
10
|
+
# for now only drives Neovim via [neovim-remote][nvr]. We should also support
|
11
|
+
# treating `tmux` as an editor, and launching tmux sessions and creating
|
12
|
+
# windows and splits as such.
|
13
|
+
#
|
14
|
+
# @example define my editor
|
15
|
+
#
|
16
|
+
# [nvr]: https://github.com/mhinz/neovim-remote
|
17
|
+
module Editor
|
18
|
+
# TmuxIde is an editor that treasts a collection of Tmux splits holding an
|
19
|
+
# Nvim process as an IDE. A "session" is a Tmux window that at least
|
20
|
+
# contains an Nvim instance, although new sessions are split like this:
|
21
|
+
# -------------
|
22
|
+
# | |
|
23
|
+
# | nvim |
|
24
|
+
# | |
|
25
|
+
# |-----------|
|
26
|
+
# |$ |$ |
|
27
|
+
# | | |
|
28
|
+
# |-----------|
|
29
|
+
class TmuxIde < Service
|
30
|
+
|
31
|
+
require_service :revealer
|
32
|
+
require_service :processes
|
33
|
+
require_service :tmux
|
34
|
+
require_service :runner # needed for sub-services
|
35
|
+
require_service :lsof # needed for sub-services
|
36
|
+
require_service :terminals
|
37
|
+
|
38
|
+
def initialize(svcs = {})
|
39
|
+
super(svcs)
|
40
|
+
@tmux_memo = ::Appear::Util::Memoizer.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check if a child path is contained by a parent path.
|
44
|
+
# @param parent [String]
|
45
|
+
# @param child [String]
|
46
|
+
# @return [Boolean]
|
47
|
+
#
|
48
|
+
# as dumb as they come
|
49
|
+
# TODO: use a real path_contains algorithm.
|
50
|
+
def path_contains?(parent, child)
|
51
|
+
p, c = Pathname.new(parent), Pathname.new(child)
|
52
|
+
c.expand_path.to_s.start_with?(p.expand_path.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Find the appropriate Nvim session for a given filename. First, we try
|
56
|
+
# to find a session actually editing this file. If none exists, we find
|
57
|
+
# the session with the deepest CWD that contains the filename.
|
58
|
+
#
|
59
|
+
# @param filename [String]
|
60
|
+
# @return [::Appear::Editor::Nvim, nil]
|
61
|
+
def find_nvim_for_file(filename)
|
62
|
+
update_nvims
|
63
|
+
cwd_to_nvim = {}
|
64
|
+
|
65
|
+
@nvims.each do |_, nvim|
|
66
|
+
return nvim if nvim.find_buffer(filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
match = @cwd_by_depth.find { |cwd| path_contains?(cwd, filename) }
|
70
|
+
return nil unless match
|
71
|
+
@cwd_to_nvim[match]
|
72
|
+
end
|
73
|
+
|
74
|
+
# find the tmux pane holding an nvim editor instance.
|
75
|
+
#
|
76
|
+
# @param nvim [Appear::Editor::Nvim]
|
77
|
+
# @return [Appear::Tmux::Pane, nil] the pane, or nil if not found
|
78
|
+
def find_tmux_pane(nvim)
|
79
|
+
@tmux_memo.call(nvim) do
|
80
|
+
tree = services.processes.process_tree(nvim.pid)
|
81
|
+
tmux_server = tree.find { |p| p.name == 'tmux' }
|
82
|
+
next nil unless tmux_server
|
83
|
+
|
84
|
+
# the first join should be the tmux pane holding our
|
85
|
+
# nvim session.
|
86
|
+
proc_and_panes = Util::Join.join(:pid, services.tmux.panes, tree)
|
87
|
+
pane_join = proc_and_panes.first
|
88
|
+
next nil unless pane_join
|
89
|
+
|
90
|
+
# new method on join: let's you get an underlying
|
91
|
+
# object out of the join if it matches a predicate.
|
92
|
+
next pane_join.unjoin do |o|
|
93
|
+
o.is_a? ::Appear::Tmux::Pane
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Find or create an IDE, then open this file in it.
|
99
|
+
#
|
100
|
+
# @param filename [String]
|
101
|
+
def find_or_create_ide(filename)
|
102
|
+
nvim = find_nvim_for_file(filename)
|
103
|
+
return nvim, find_tmux_pane(nvim) unless nvim.nil?
|
104
|
+
create_ide(filename)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create a new IDE instance editing `filename`
|
108
|
+
#
|
109
|
+
# @param filename [String]
|
110
|
+
def create_ide(filename)
|
111
|
+
dir = project_root(filename)
|
112
|
+
|
113
|
+
# find or create session
|
114
|
+
tmux_session = services.tmux.sessions.sort_by { |s| s.windows.length }.last
|
115
|
+
tmux_session ||= services.tmux.new_session(
|
116
|
+
# -c: current directory
|
117
|
+
:c => dir
|
118
|
+
)
|
119
|
+
|
120
|
+
# find or create window
|
121
|
+
window = tmux_session.windows.find do |win|
|
122
|
+
win.panes.first.current_path == dir
|
123
|
+
end
|
124
|
+
|
125
|
+
window ||= tmux_session.new_window(
|
126
|
+
# -c: current directory
|
127
|
+
:c => dir,
|
128
|
+
# -d: do not focus
|
129
|
+
:d => true,
|
130
|
+
)
|
131
|
+
|
132
|
+
# remember our pid list
|
133
|
+
existing_nvims = services.processes.pgrep(Nvim::NEOVIM)
|
134
|
+
|
135
|
+
# split window across the middle, into a big and little pane
|
136
|
+
main = window.panes.first
|
137
|
+
main.send_keys([Nvim.edit_command(filename).to_s, "\n"], :l => true)
|
138
|
+
left = main.split(:p => 30, :v => true, :c => dir)
|
139
|
+
# cut the smaller bottom pane in half
|
140
|
+
right = left.split(:p => 50, :h => true, :c => dir)
|
141
|
+
# put a vim in the top pane, and select it
|
142
|
+
#[left, right].each_with_index do |pane, idx|
|
143
|
+
#pane.send_keys(["bottom pane ##{idx}"], :l => true)
|
144
|
+
#end
|
145
|
+
|
146
|
+
# Hacky way to wait for nvim to launch! This should take at most 2
|
147
|
+
# seconds, otherwise your vim is launching too slowley ;)
|
148
|
+
wait_until(2) { (services.processes.pgrep(Nvim::NEOVIM) - existing_nvims).length >= 1 }
|
149
|
+
|
150
|
+
nvim = find_nvim_for_file(filename)
|
151
|
+
return nvim, find_tmux_pane(nvim)
|
152
|
+
end
|
153
|
+
|
154
|
+
def wait_until(max_duration, sleep = 0.1)
|
155
|
+
raise ArgumentError.new("no block given") unless block_given?
|
156
|
+
start = Time.new
|
157
|
+
limit = start + max_duration
|
158
|
+
iters = 0
|
159
|
+
while Time.new < limit
|
160
|
+
if yield
|
161
|
+
log("wait_until(max_duration=#{max_duration}, sleep=#{sleep}) slept #{iters} times, took #{Time.new - start}s")
|
162
|
+
return true
|
163
|
+
end
|
164
|
+
iters = iters + 1
|
165
|
+
sleep(sleep)
|
166
|
+
end
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
# reveal files in an existing or new IDE session
|
171
|
+
#
|
172
|
+
# @param filenames [Array<String>]
|
173
|
+
def call(*filenames)
|
174
|
+
nvims = []
|
175
|
+
nvim_to_session = {}
|
176
|
+
|
177
|
+
filenames.each do |filename|
|
178
|
+
filename = File.expand_path(filename)
|
179
|
+
nvim, pane = find_or_create_ide(filename)
|
180
|
+
# focuses the file in the nvim instance, or start editing it.
|
181
|
+
Thread.new { nvim.drop(filename) }
|
182
|
+
nvims << nvim unless nvims.include?(nvim)
|
183
|
+
nvim_to_session[nvim] = pane.session
|
184
|
+
end
|
185
|
+
|
186
|
+
nvims.map do |nvim|
|
187
|
+
Thread.new do
|
188
|
+
# go ahead and reveal our nvim
|
189
|
+
next true if services.revealer.call(nvim.pid)
|
190
|
+
|
191
|
+
session = nvim_to_session[nvim]
|
192
|
+
# if we didn't return, we need to create a Tmux client for our
|
193
|
+
# session.
|
194
|
+
command = services.tmux.attach_session_command(session)
|
195
|
+
terminal = services.terminals.get
|
196
|
+
term_pane = terminal.new_window(command.to_s)
|
197
|
+
terminal.reveal_pane(term_pane)
|
198
|
+
end
|
199
|
+
end.each(&:join)
|
200
|
+
|
201
|
+
log "#{self.class}: finito."
|
202
|
+
end
|
203
|
+
|
204
|
+
# Guess the project root for a given path by inspecting its parent
|
205
|
+
# directories for certain markers like git roots.
|
206
|
+
#
|
207
|
+
# @param filename [String]
|
208
|
+
# @return [String] some path
|
209
|
+
def project_root(filename)
|
210
|
+
# TODO: a real constant? Some internet-provided list?
|
211
|
+
# these are files that indicate the root of a project
|
212
|
+
markers = %w(.git .hg Gemfile package.json setup.py README README.md)
|
213
|
+
p = Pathname.new(filename).expand_path
|
214
|
+
p.ascend do |path|
|
215
|
+
is_root = markers.any? do |marker|
|
216
|
+
path.join(marker).exist?
|
217
|
+
end
|
218
|
+
|
219
|
+
return path if is_root
|
220
|
+
end
|
221
|
+
|
222
|
+
# no markers were found
|
223
|
+
return p.to_s if p.directory?
|
224
|
+
return p.dirname.to_s
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
# creates a cached mapping of Nvim-related information for maximum lookup
|
230
|
+
# speed.
|
231
|
+
def update_nvims
|
232
|
+
@nvims ||= {}
|
233
|
+
@nvim_to_cwd ||= {}
|
234
|
+
@cwd_to_nvim ||= {}
|
235
|
+
new_nvims = false
|
236
|
+
|
237
|
+
Nvim.sockets.each do |sock|
|
238
|
+
next if @nvims[sock]
|
239
|
+
|
240
|
+
new_nvims = true
|
241
|
+
nvim = Nvim.new(sock, services)
|
242
|
+
@nvims[sock] = nvim
|
243
|
+
cwd = nvim.cwd
|
244
|
+
@nvim_to_cwd[nvim] = cwd
|
245
|
+
@cwd_to_nvim[cwd] = nvim
|
246
|
+
end
|
247
|
+
|
248
|
+
if new_nvims
|
249
|
+
@cwd_by_depth = @cwd_to_nvim.keys.sort_by { |d| Pathname.new(d).each_filename.to_a.length }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
# List of all editors. In the future, we should provide more editor support
|
256
|
+
# and extend this list, and then allow the user to choose the editor to
|
257
|
+
# Appear in.
|
258
|
+
ALL = [TmuxIde]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
require 'appear/editor/nvim'
|
data/lib/appear/instance.rb
CHANGED
@@ -32,6 +32,10 @@ module Appear
|
|
32
32
|
@all_services[:lsof] = Appear::Lsof.new(@all_services)
|
33
33
|
@all_services[:mac_os] = Appear::MacOs.new(@all_services)
|
34
34
|
@all_services[:tmux] = Appear::Tmux.new(@all_services)
|
35
|
+
@all_services[:terminals] = Appear::Terminals.new([
|
36
|
+
Appear::Terminal::Iterm2.new(@all_services),
|
37
|
+
Appear::Terminal::TerminalApp.new(@all_services),
|
38
|
+
])
|
35
39
|
|
36
40
|
# make sure we can use our processes service, and log stuff.
|
37
41
|
super(@all_services)
|