appear 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)