yap-shell 0.1.1 → 0.3.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 +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/WISHLIST.md +14 -0
- data/addons/history/Gemfile +2 -0
- data/addons/history/history.rb +101 -0
- data/addons/history/lib/history/buffer.rb +204 -0
- data/addons/history/lib/history/events.rb +13 -0
- data/addons/keyboard_macros/keyboard_macros.rb +295 -0
- data/addons/prompt/Gemfile +1 -0
- data/addons/prompt/right_prompt.rb +17 -0
- data/addons/prompt_updates/prompt_updates.rb +28 -0
- data/addons/tab_completion/Gemfile +0 -0
- data/addons/tab_completion/lib/tab_completion/completer.rb +62 -0
- data/addons/tab_completion/lib/tab_completion/custom_completion.rb +33 -0
- data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +7 -0
- data/addons/tab_completion/lib/tab_completion/file_completion.rb +75 -0
- data/addons/tab_completion/tab_completion.rb +157 -0
- data/bin/yap +13 -4
- data/lib/tasks/addons.rake +51 -0
- data/lib/yap.rb +4 -55
- data/lib/yap/shell.rb +51 -10
- data/lib/yap/shell/builtins.rb +2 -2
- data/lib/yap/shell/builtins/alias.rb +2 -2
- data/lib/yap/shell/builtins/cd.rb +9 -11
- data/lib/yap/shell/builtins/env.rb +11 -0
- data/lib/yap/shell/commands.rb +29 -18
- data/lib/yap/shell/evaluation.rb +185 -68
- data/lib/yap/shell/evaluation/shell_expansions.rb +85 -0
- data/lib/yap/shell/event_emitter.rb +18 -0
- data/lib/yap/shell/execution/builtin_command_execution.rb +1 -1
- data/lib/yap/shell/execution/command_execution.rb +3 -3
- data/lib/yap/shell/execution/context.rb +32 -9
- data/lib/yap/shell/execution/file_system_command_execution.rb +12 -7
- data/lib/yap/shell/execution/ruby_command_execution.rb +6 -6
- data/lib/yap/shell/execution/shell_command_execution.rb +17 -2
- data/lib/yap/shell/prompt.rb +21 -0
- data/lib/yap/shell/repl.rb +179 -18
- data/lib/yap/shell/version.rb +1 -1
- data/lib/yap/world.rb +149 -15
- data/lib/yap/world/addons.rb +135 -0
- data/rcfiles/.yaprc +240 -10
- data/test.rb +206 -0
- data/update-rawline.sh +6 -0
- data/yap-shell.gemspec +11 -3
- metadata +101 -10
- data/addons/history.rb +0 -171
@@ -0,0 +1,85 @@
|
|
1
|
+
module Yap::Shell
|
2
|
+
class Evaluation
|
3
|
+
class ShellExpansions
|
4
|
+
attr_reader :aliases, :world
|
5
|
+
|
6
|
+
def initialize(world:, aliases: Aliases.instance)
|
7
|
+
@world = world
|
8
|
+
@aliases = aliases
|
9
|
+
end
|
10
|
+
|
11
|
+
def expand_aliases_in(input)
|
12
|
+
head, *tail = input.split(/\s/, 2).first
|
13
|
+
if new_head=aliases.fetch_alias(head)
|
14
|
+
[new_head].concat(tail).join(" ")
|
15
|
+
else
|
16
|
+
input
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def expand_words_in(input, escape_directory_expansions: true)
|
21
|
+
[input].flatten.inject([]) do |results,str|
|
22
|
+
results << process_expansions(
|
23
|
+
word_expand(str),
|
24
|
+
escape_directory_expansions: escape_directory_expansions
|
25
|
+
)
|
26
|
+
end.flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
def expand_variables_in(input)
|
30
|
+
env_expand(input)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def env_expand(str)
|
36
|
+
str.gsub(/\$(\S+)/) do |match,*args|
|
37
|
+
var_name = match[1..-1]
|
38
|
+
case var_name
|
39
|
+
when "?"
|
40
|
+
world.last_result ? world.last_result.status_code.to_s : '0'
|
41
|
+
else
|
42
|
+
world.env.fetch(var_name){ match }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def word_expand(str)
|
48
|
+
content = str.scan(/\{([^\}]+)\}/).flatten.first
|
49
|
+
if content
|
50
|
+
expansions = content.split(",", -1)
|
51
|
+
|
52
|
+
# Be compatible with Bash/Zsh which only do word-expansion if there
|
53
|
+
# at least one comma listed. E.g. "a_{1,2}" => "a_1 a_2" whereas
|
54
|
+
# "a_{1}" => "a_{1}"
|
55
|
+
if expansions.length > 1
|
56
|
+
return expansions.map { |expansion| str.sub(/\{([^\}]+)\}/, expansion) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
return [str]
|
60
|
+
end
|
61
|
+
|
62
|
+
def process_expansions(expansions, escape_directory_expansions: true)
|
63
|
+
expansions.map do |s|
|
64
|
+
# Basic bash-style tilde expansion
|
65
|
+
s.gsub!(/\A~(.*)/, world.env["HOME"] + '\1')
|
66
|
+
|
67
|
+
# Basic bash-style variable expansion
|
68
|
+
s = env_expand(s)
|
69
|
+
|
70
|
+
# Basic bash-style path-name expansion
|
71
|
+
expansions = Dir[s]
|
72
|
+
if expansions.any?
|
73
|
+
if escape_directory_expansions
|
74
|
+
expansions.map(&:shellescape)
|
75
|
+
else
|
76
|
+
expansions
|
77
|
+
end
|
78
|
+
else
|
79
|
+
s
|
80
|
+
end
|
81
|
+
end.flatten
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Yap::Shell
|
2
|
+
module EventEmitter
|
3
|
+
def _callbacks
|
4
|
+
@_callbacks ||= Hash.new { |h, k| h[k] = [] }
|
5
|
+
end
|
6
|
+
|
7
|
+
def on(type, *args, &blk)
|
8
|
+
_callbacks[type] << blk
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def emit(type, *args)
|
13
|
+
_callbacks[type].each do |blk|
|
14
|
+
blk.call(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -2,7 +2,7 @@ require 'yap/shell/execution/result'
|
|
2
2
|
|
3
3
|
module Yap::Shell::Execution
|
4
4
|
class BuiltinCommandExecution < CommandExecution
|
5
|
-
on_execute do |command:, n:, of:|
|
5
|
+
on_execute do |command:, n:, of:, wait:|
|
6
6
|
status_code = command.execute(stdin:@stdin, stdout:@stdout, stderr:@stderr)
|
7
7
|
if status_code == :resume
|
8
8
|
ResumeExecution.new(status_code:0, directory:Dir.pwd, n:n, of:of)
|
@@ -10,14 +10,14 @@ module Yap::Shell::Execution
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def initialize(stdin:, stdout:, stderr:,world:)
|
13
|
+
def initialize(stdin:, stdout:, stderr:, world:)
|
14
14
|
@stdin, @stdout, @stderr = stdin, stdout, stderr
|
15
15
|
@world = world
|
16
16
|
end
|
17
17
|
|
18
|
-
def execute(command:, n:, of:)
|
18
|
+
def execute(command:, n:, of:, wait:true)
|
19
19
|
if self.class.on_execute
|
20
|
-
self.instance_exec(command:command, n:n, of:of, &self.class.on_execute)
|
20
|
+
self.instance_exec(command:command, n:n, of:of, wait:wait, &self.class.on_execute)
|
21
21
|
else
|
22
22
|
raise NotImplementedError, "on_execute block hasn't been implemented!"
|
23
23
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module Yap::Shell::Execution
|
2
4
|
class Context
|
3
5
|
def self.on(event=nil, &blk)
|
@@ -31,8 +33,8 @@ module Yap::Shell::Execution
|
|
31
33
|
@suspended_execution_contexts = []
|
32
34
|
end
|
33
35
|
|
34
|
-
def add_command_to_run(command, stdin:, stdout:, stderr:)
|
35
|
-
@command_queue << [command, stdin, stdout, stderr]
|
36
|
+
def add_command_to_run(command, stdin:, stdout:, stderr:, wait:)
|
37
|
+
@command_queue << [command, stdin, stdout, stderr, wait]
|
36
38
|
end
|
37
39
|
|
38
40
|
def clear_commands
|
@@ -41,9 +43,9 @@ module Yap::Shell::Execution
|
|
41
43
|
|
42
44
|
def execute(world:)
|
43
45
|
results = []
|
44
|
-
@command_queue.each_with_index do |(command, stdin, stdout, stderr), reversed_i|
|
46
|
+
@command_queue.each_with_index do |(command, stdin, stdout, stderr, wait), reversed_i|
|
45
47
|
of = @command_queue.length
|
46
|
-
i =
|
48
|
+
i = reversed_i + 1
|
47
49
|
stdin = @stdin if stdin == :stdin
|
48
50
|
stdout = @stdout if stdout == :stdout
|
49
51
|
stderr = @stderr if stderr == :stderr
|
@@ -58,11 +60,32 @@ module Yap::Shell::Execution
|
|
58
60
|
)
|
59
61
|
|
60
62
|
@saved_tty_attrs = Termios.tcgetattr(STDIN)
|
61
|
-
self.class.fire :before_execute,
|
62
|
-
|
63
|
-
|
63
|
+
self.class.fire :before_execute, world, command: command
|
64
|
+
|
65
|
+
begin
|
66
|
+
result = execution_context.execute(command:command, n:i, of:of, wait:wait)
|
67
|
+
rescue Exception => ex
|
68
|
+
raise(ex) if ex.is_a?(SystemExit)
|
69
|
+
puts <<-ERROR.gsub(/^\s*\|/, '')
|
70
|
+
|******************************
|
71
|
+
|\e[31mWhoops! An unexpected error has occurred\e[0m
|
72
|
+
|******************************
|
73
|
+
|
|
74
|
+
|The error was:
|
75
|
+
| #{ex.message}
|
76
|
+
|
|
77
|
+
|Backtrace:
|
78
|
+
|#{ex.backtrace.join("\n")}
|
79
|
+
|
|
80
|
+
|Report this to yap-shell on github:
|
81
|
+
| https://github.com/zdennis/yap-shell/issues/new?title=#{URI.escape(ex.message)}
|
82
|
+
|
|
83
|
+
ERROR
|
84
|
+
end
|
85
|
+
|
86
|
+
self.class.fire :after_execute, world, command: command, result: result
|
64
87
|
|
65
|
-
results << process_execution_result(execution_context:execution_context, result:
|
88
|
+
results << process_execution_result(execution_context:execution_context, result:result)
|
66
89
|
Termios.tcsetattr(STDIN, Termios::TCSANOW, @saved_tty_attrs)
|
67
90
|
end
|
68
91
|
end
|
@@ -86,7 +109,7 @@ module Yap::Shell::Execution
|
|
86
109
|
nresult = execution_context.resume
|
87
110
|
return process_execution_result execution_context: execution_context, result: nresult
|
88
111
|
else
|
89
|
-
stderr.puts "fg: No such job"
|
112
|
+
@stderr.puts "fg: No such job"
|
90
113
|
end
|
91
114
|
else
|
92
115
|
return result
|
@@ -3,7 +3,7 @@ require 'termios'
|
|
3
3
|
|
4
4
|
module Yap::Shell::Execution
|
5
5
|
class FileSystemCommandExecution < CommandExecution
|
6
|
-
on_execute do |command:, n:, of:, resume_blk:nil|
|
6
|
+
on_execute do |command:, n:, of:, wait:, resume_blk:nil|
|
7
7
|
stdin, stdout, stderr, world = @stdin, @stdout, @stderr, @world
|
8
8
|
result = nil
|
9
9
|
if resume_blk
|
@@ -40,7 +40,13 @@ module Yap::Shell::Execution
|
|
40
40
|
$stdout.reopen stdout
|
41
41
|
$stderr.reopen stderr
|
42
42
|
|
43
|
-
|
43
|
+
begin
|
44
|
+
before = ENV.to_h.dup
|
45
|
+
ENV.replace(@world.env)
|
46
|
+
Kernel.exec command.to_executable_str
|
47
|
+
ensure
|
48
|
+
ENV.replace(before)
|
49
|
+
end
|
44
50
|
end
|
45
51
|
|
46
52
|
# Put the child process into a process group of its own
|
@@ -54,10 +60,9 @@ module Yap::Shell::Execution
|
|
54
60
|
|
55
61
|
# Set terminal's process group to that of the child process
|
56
62
|
Termios.tcsetpgrp STDIN, pid
|
57
|
-
pid, status = Process.wait2(
|
58
|
-
puts "Process (#{pid}) stopped: #{status.inspect}" if ENV["DEBUG"]
|
63
|
+
pid, status = Process.wait2(pid, Process::WUNTRACED) if wait
|
59
64
|
|
60
|
-
# If we're not printing to the terminal
|
65
|
+
# If we're not printing to the terminal then close in/out/err. This
|
61
66
|
# is so the next command in the pipeline can complete and don't hang waiting for
|
62
67
|
# stdin after the command that's writing to its stdin has completed.
|
63
68
|
if stdout != $stdout && stdout.is_a?(IO) && !stdout.closed? then
|
@@ -78,7 +83,7 @@ module Yap::Shell::Execution
|
|
78
83
|
end
|
79
84
|
|
80
85
|
# if the reason we stopped is from being suspended
|
81
|
-
if status.stopsig == Signal.list["TSTP"]
|
86
|
+
if status && status.stopsig == Signal.list["TSTP"]
|
82
87
|
puts "Process (#{pid}) suspended: #{status.stopsig}" if ENV["DEBUG"]
|
83
88
|
suspended(command:command, n:n, of:of, pid: pid)
|
84
89
|
result = Yap::Shell::Execution::SuspendExecution.new(status_code:nil, directory:Dir.pwd, n:n, of:of)
|
@@ -100,7 +105,7 @@ module Yap::Shell::Execution
|
|
100
105
|
args[:pid]
|
101
106
|
end
|
102
107
|
|
103
|
-
self.instance_exec command:args[:command], n:args[:n], of:args[:of], resume_blk:resume_blk, &self.class.on_execute
|
108
|
+
self.instance_exec command:args[:command], n:args[:n], of:args[:of], resume_blk:resume_blk, wait:true, &self.class.on_execute
|
104
109
|
end
|
105
110
|
|
106
111
|
def suspended(command:, n:, of:, pid:)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Yap::Shell::Execution
|
2
2
|
class RubyCommandExecution < CommandExecution
|
3
|
-
on_execute do |command:, n:, of:|
|
3
|
+
on_execute do |command:, n:, of:, wait:|
|
4
4
|
result = nil
|
5
5
|
stdin, stdout, stderr, world = @stdin, @stdout, @stderr, @world
|
6
|
-
t = Thread.new {
|
6
|
+
# t = Thread.new {
|
7
7
|
exit_code = 0
|
8
8
|
first_command = n == 1
|
9
9
|
|
@@ -72,13 +72,13 @@ module Yap::Shell::Execution
|
|
72
72
|
# to be scheduled before we send back our status code. This could
|
73
73
|
# probably use a more elaborate signal or message passing scheme,
|
74
74
|
# but that's for another day.
|
75
|
-
Thread.pass
|
75
|
+
# Thread.pass
|
76
76
|
|
77
77
|
# Make up an exit code
|
78
78
|
result = Result.new(status_code:exit_code, directory:Dir.pwd, n:n, of:of)
|
79
|
-
}
|
80
|
-
t.abort_on_exception = true
|
81
|
-
t.join
|
79
|
+
# }
|
80
|
+
# t.abort_on_exception = true
|
81
|
+
# t.join
|
82
82
|
result
|
83
83
|
end
|
84
84
|
end
|
@@ -1,8 +1,23 @@
|
|
1
1
|
module Yap::Shell::Execution
|
2
2
|
class ShellCommandExecution < CommandExecution
|
3
|
-
on_execute do |command:, n:, of:|
|
3
|
+
on_execute do |command:, n:, of:, wait:|
|
4
|
+
possible_parameters = {
|
5
|
+
command: command.str,
|
6
|
+
args: command.args,
|
7
|
+
stdin: @stdin,
|
8
|
+
stdout: @stdout,
|
9
|
+
stderr: @stderr,
|
10
|
+
world: @world,
|
11
|
+
line: command.line
|
12
|
+
}
|
13
|
+
|
4
14
|
func = command.to_proc
|
5
|
-
|
15
|
+
params = func.parameters.reduce({}) do |h, (type, name)|
|
16
|
+
h[name] = possible_parameters[name]
|
17
|
+
h
|
18
|
+
end
|
19
|
+
|
20
|
+
command_result = func.call(**params)
|
6
21
|
@stdout.close if @stdout != $stdout && !@stdout.closed?
|
7
22
|
@stderr.close if @stderr != $stderr && !@stderr.closed?
|
8
23
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Yap::Shell
|
2
|
+
class Prompt
|
3
|
+
attr_reader :text
|
4
|
+
|
5
|
+
def initialize(text:, &blk)
|
6
|
+
@text = text
|
7
|
+
@blk = blk
|
8
|
+
end
|
9
|
+
|
10
|
+
def text=(text)
|
11
|
+
@text = text
|
12
|
+
end
|
13
|
+
|
14
|
+
def update
|
15
|
+
if @blk
|
16
|
+
@text = @blk.call
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/yap/shell/repl.rb
CHANGED
@@ -1,38 +1,198 @@
|
|
1
|
-
require '
|
1
|
+
require 'shellwords'
|
2
|
+
require 'term/ansicolor'
|
2
3
|
|
3
4
|
module Yap::Shell
|
5
|
+
module Color
|
6
|
+
extend Term::ANSIColor
|
7
|
+
end
|
8
|
+
|
4
9
|
class Repl
|
10
|
+
attr_reader :editor
|
11
|
+
|
5
12
|
def initialize(world:nil)
|
6
13
|
@world = world
|
14
|
+
@editor= world.editor
|
15
|
+
install_default_keybindings
|
16
|
+
install_default_tab_completion_proc
|
7
17
|
end
|
8
18
|
|
9
|
-
def
|
19
|
+
def on_input(&blk)
|
10
20
|
@blk = blk
|
11
21
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
22
|
+
@world.editor.on_read_line do |event|
|
23
|
+
# editor.history = true?
|
24
|
+
line = event[:payload][:line]
|
16
25
|
begin
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
next if input == ""
|
21
|
-
|
22
|
-
input = process_heredoc(input)
|
23
|
-
|
24
|
-
yield input
|
26
|
+
@blk.call(line)
|
27
|
+
@world.editor.redraw_prompt
|
25
28
|
rescue ::Yap::Shell::CommandUnknownError => ex
|
26
29
|
puts " CommandError: #{ex.message}"
|
27
|
-
|
28
|
-
|
29
|
-
next
|
30
|
+
ensure
|
31
|
+
@world.editor.reset_line
|
30
32
|
end
|
33
|
+
|
34
|
+
ensure_process_group_controls_the_tty
|
35
|
+
@world.refresh_prompt
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
34
39
|
private
|
35
40
|
|
41
|
+
def kill_ring
|
42
|
+
@kill_ring ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def install_default_keybindings
|
46
|
+
editor.terminal.keys.merge!(enter: [13])
|
47
|
+
editor.bind(:return){ editor.newline }
|
48
|
+
|
49
|
+
# Move to beginning of line
|
50
|
+
editor.bind(:ctrl_a) { editor.move_to_beginning_of_input }
|
51
|
+
|
52
|
+
# Move to end of line
|
53
|
+
editor.bind(:ctrl_e) { editor.move_to_end_of_input }
|
54
|
+
|
55
|
+
# Move backward one word at a time
|
56
|
+
editor.bind(:ctrl_b) {
|
57
|
+
text = editor.line.text[0...editor.line.position].reverse
|
58
|
+
position = text.index(/\s+/, 1)
|
59
|
+
position = position ? (text.length - position) : 0
|
60
|
+
editor.move_to_position position
|
61
|
+
}
|
62
|
+
|
63
|
+
# Move forward one word at a time
|
64
|
+
editor.bind(:ctrl_f) {
|
65
|
+
text = editor.line.text
|
66
|
+
position = text.index(/\s+/, editor.line.position)
|
67
|
+
position = position ? (position + 1) : text.length
|
68
|
+
editor.move_to_position position
|
69
|
+
}
|
70
|
+
|
71
|
+
# Yank text from the kill ring and insert it at the cursor position
|
72
|
+
editor.bind(:ctrl_y){
|
73
|
+
text = kill_ring[-1]
|
74
|
+
if text
|
75
|
+
editor.yank_forward text.without_ansi
|
76
|
+
end
|
77
|
+
}
|
78
|
+
|
79
|
+
# Backwards delete one word
|
80
|
+
editor.bind(:ctrl_w){
|
81
|
+
before_text = editor.line.text[0...editor.line.position]
|
82
|
+
after_text = editor.line.text[editor.line.position..-1]
|
83
|
+
|
84
|
+
have_only_seen_whitespace = true
|
85
|
+
position = 0
|
86
|
+
|
87
|
+
before_text.reverse.each_char.with_index do |ch, i|
|
88
|
+
if ch =~ /\s/ && !have_only_seen_whitespace
|
89
|
+
position = before_text.length - i
|
90
|
+
break
|
91
|
+
else
|
92
|
+
have_only_seen_whitespace = false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
killed_text = before_text[position...editor.line.position]
|
97
|
+
kill_ring.push killed_text
|
98
|
+
|
99
|
+
text = [before_text.slice(0, position), after_text].join
|
100
|
+
editor.overwrite_line text
|
101
|
+
editor.move_to_position position
|
102
|
+
}
|
103
|
+
|
104
|
+
# History forward, but if at the end of the history then give user a
|
105
|
+
# blank line rather than remain on the last command
|
106
|
+
editor.bind(:down_arrow) {
|
107
|
+
if editor.history.searching? && !editor.history.end?
|
108
|
+
editor.history_forward
|
109
|
+
else
|
110
|
+
editor.overwrite_line ""
|
111
|
+
end
|
112
|
+
}
|
113
|
+
|
114
|
+
editor.bind(:enter) { editor.newline }
|
115
|
+
editor.bind(:tab) { editor.complete }
|
116
|
+
editor.bind(:backspace) { editor.delete_left_character }
|
117
|
+
|
118
|
+
# Delete to end of line from cursor position
|
119
|
+
editor.bind(:ctrl_k) {
|
120
|
+
kill_ring.push editor.kill_forward
|
121
|
+
}
|
122
|
+
|
123
|
+
# Delete to beginning of line from cursor position
|
124
|
+
editor.bind(:ctrl_u) {
|
125
|
+
kill_ring.push editor.line.text[0...editor.line.position]
|
126
|
+
editor.overwrite_line editor.line.text[editor.line.position..-1]
|
127
|
+
editor.move_to_position 0
|
128
|
+
}
|
129
|
+
|
130
|
+
# Forward delete a character, leaving the cursor in place
|
131
|
+
editor.bind("\e[3~") {
|
132
|
+
before_text = editor.line.text[0...editor.line.position]
|
133
|
+
after_text = editor.line.text[(editor.line.position+1)..-1]
|
134
|
+
text = [before_text, after_text].join
|
135
|
+
position = editor.line.position
|
136
|
+
editor.overwrite_line text
|
137
|
+
editor.move_to_position position
|
138
|
+
}
|
139
|
+
|
140
|
+
editor.bind(:ctrl_l){
|
141
|
+
editor.clear_screen
|
142
|
+
}
|
143
|
+
|
144
|
+
editor.bind(:ctrl_r) {
|
145
|
+
$r = $r ? false : true
|
146
|
+
# editor.redo
|
147
|
+
}
|
148
|
+
editor.bind(:left_arrow) { editor.move_left }
|
149
|
+
editor.bind(:right_arrow) { editor.move_right }
|
150
|
+
editor.bind(:up_arrow) { editor.history_back }
|
151
|
+
editor.bind(:down_arrow) { editor.history_forward }
|
152
|
+
editor.bind(:delete) { editor.delete_character }
|
153
|
+
editor.bind(:insert) { editor.toggle_mode }
|
154
|
+
|
155
|
+
editor.bind(:ctrl_g) { editor.clear_history }
|
156
|
+
# editor.bind(:ctrl_l) { editor.debug_line }
|
157
|
+
editor.bind(:ctrl_h) { editor.show_history }
|
158
|
+
editor.bind(:ctrl_d) { puts; puts "Exiting..."; exit }
|
159
|
+
|
160
|
+
# character-search; wraps around as necessary
|
161
|
+
editor.bind(:ctrl_n) {
|
162
|
+
line = editor.line
|
163
|
+
text, start_position = line.text, line.position
|
164
|
+
i, new_position = start_position, nil
|
165
|
+
|
166
|
+
break_on_bytes = [editor.terminal.keys[:ctrl_c]].flatten
|
167
|
+
byte = [editor.read_character].flatten.first
|
168
|
+
|
169
|
+
unless break_on_bytes.include?(byte)
|
170
|
+
loop do
|
171
|
+
i += 1
|
172
|
+
i = 0 if i >= text.length # wrap-around to the beginning
|
173
|
+
break if i == start_position # back to where we started
|
174
|
+
(editor.move_to_position(i) ; break) if text[i] == byte.chr # found a match; move and break
|
175
|
+
end
|
176
|
+
end
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def install_default_tab_completion_proc
|
181
|
+
editor.completion_proc = lambda do |word|
|
182
|
+
Dir["#{word}*"].map{ |str| str.gsub(/ /, '\ ')}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# This is to prevent the Errno::EIO error from occurring by ensuring that
|
187
|
+
# if we haven't been made the process group controlling the TTY that we
|
188
|
+
# become so. This method intentionally blocks.
|
189
|
+
def ensure_process_group_controls_the_tty
|
190
|
+
while Process.pid != Termios.tcgetpgrp(STDIN)
|
191
|
+
Termios.tcsetpgrp(STDIN, Process.pid)
|
192
|
+
sleep 0.1
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
36
196
|
def process_heredoc(_input)
|
37
197
|
if _input =~ /<<-?([A-z0-9\-]+)\s*$/
|
38
198
|
input = _input.dup
|
@@ -43,8 +203,9 @@ module Yap::Shell
|
|
43
203
|
end
|
44
204
|
|
45
205
|
puts "Beginning heredoc" if ENV["DEBUG"]
|
206
|
+
|
46
207
|
loop do
|
47
|
-
str =
|
208
|
+
str = editor.read(@world.secondary_prompt.update.text, false)
|
48
209
|
input << "#{str}\n"
|
49
210
|
if str =~ /^#{Regexp.escape(marker)}$/
|
50
211
|
puts "Ending heredoc" if ENV["DEBUG"]
|