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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d114dfa1bdcda18b13325f5d7961b296b09a8b2
4
- data.tar.gz: 26fcebb685c8a0adcc5cac80c9bd1a7ce3153eef
3
+ metadata.gz: 67b9aebb07fd1277e44209ecc142ce4266b2a614
4
+ data.tar.gz: e234f31f3e772957cf50967eae4ceb633ee1951c
5
5
  SHA512:
6
- metadata.gz: 12f5f608f578ae6042e987fd20a5082d70c4b8067bfaebaf3e033fcacf66ede064bec3892d1174bf3be16a3168a91b80095ec798354d63f79e067609ab577eef
7
- data.tar.gz: 848a59a97371e61417851e9fe8cdf33436d1e4e5ccf9a5d9ec9af0618c091d197babf493c48332ec0f5b975408acda7f0b02513582cf9b929334d74afb345a96
6
+ metadata.gz: 0caad8654c99c2d4b98fe759fb37ad8919443ce332d638af1459494f3614887336ecaf38550ba733bd4a85558c0672e479e2762d16dd1e0619c6b70d1d5ee2d7
7
+ data.tar.gz: 3c98564f230a8a7271920b122d583628155411fa76c0e62b2a49efc9fa677b36b8c49f9ba4dcb30f1a2e6f75f65ed372ec08a7ee4521fa0ec87ef55d003274e0
data/.doc-coverage CHANGED
@@ -1 +1 @@
1
- 80.43
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
@@ -55,7 +55,7 @@ GNU Screen support is a non-goal. It's time for screen users to switch to tmux.
55
55
 
56
56
  ## system requirements
57
57
 
58
- - `ruby` >= 2
58
+ - `ruby` >= 1.9.3
59
59
  - `lsof` command
60
60
  - `ps` command
61
61
  - `pgrep` command
data/bin/appear CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'pathname'
3
3
 
4
+ # require pry in development
5
+ begin
6
+ require 'pry'
7
+ rescue LoadError
8
+ end
9
+
4
10
  begin
5
11
  require 'appear/command'
6
12
  rescue LoadError
@@ -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
@@ -2,14 +2,11 @@ require 'pathname'
2
2
 
3
3
  module Appear
4
4
  # the version of Appear
5
- VERSION = '1.1.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'
@@ -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)