yap-shell 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"]
|