diff --git a/application.py b/application.py index 327c356..8d35e68 100755 --- a/application.py +++ b/application.py @@ -128,7 +128,9 @@ class Application(object): 'lisp', 'make', 'mini', 'mutt', 'nasm', 'ocaml', 'perl', 'python', 'replace', 'rst', 'scheme', 'search', 'sh', 'sql', 'tt', 'text', 'text2', 'which', 'xml', 'cheetah', 'colortext', - 'latex', 'insertmini', 'conf', 'haskell', 'erlang') + 'latex', 'insertmini', 'conf', 'haskell', 'erlang', + 'iperl', 'iperlmini', + ) for name in names: exec("import mode.%s; mode.%s.install(self)" % (name, name)) diff --git a/buffer.py b/buffer.py index 0a22e20..85f1154 100644 --- a/buffer.py +++ b/buffer.py @@ -398,6 +398,31 @@ class ConsoleBuffer(Buffer): def readonly(self): return True +# console is another singleton +iperl = None +class IperlBuffer(Buffer): + btype = 'iperl' + modename = 'iperl' + def __new__(cls, *args, **kwargs): + global iperl + if iperl is None: + iperl = object.__new__(IperlBuffer, *args, **kwargs) + return iperl + def __init__(self): + Buffer.__init__(self) + self.clear() + def clear(self): + iperl.set_data('', force=True) + def name(self): + return '*IPerl*' + def changed(self): + return False + def close(self): + global iperl + iperl = None + def readonly(self): + return True + class BinaryDataException(Exception): pass diff --git a/mode/consolemini.py b/mode/consolemini.py index 0eba718..1a6f9b1 100644 --- a/mode/consolemini.py +++ b/mode/consolemini.py @@ -186,14 +186,15 @@ class ConsoleTab(method.Method): class ConsoleBaseMethod(method.Method): subcls = method.Method + subbuf = '*Console*' def __init__(self): method.Method.__init__(self) self.submethod = self.subcls() def _execute(self, w, **vargs): a = w.application - if not a.has_buffer_name('*Console*'): + if not a.has_buffer_name(self.subbuf): raise Exception, "No console found!" - w2 = a.bufferlist.get_buffer_by_name('*Console*').windows[0] + w2 = a.bufferlist.get_buffer_by_name(self.subbuf).windows[0] self.submethod.execute(w2, **vargs) class ConsolePageUp(ConsoleBaseMethod): diff --git a/mode/iperl.py b/mode/iperl.py new file mode 100644 index 0000000..ba82d4c --- /dev/null +++ b/mode/iperl.py @@ -0,0 +1,37 @@ +import color, mode +#from lex import Grammar, PatternRule, RegionRule +#from mode.python import StringGrammar, PythonGrammar + +#class ReprGrammar(Grammar): +# rules = [ +# RegionRule(r'string', r'"', StringGrammar, r'"'), +# RegionRule(r'string', r"'", StringGrammar, r"'"), +# ] +#class ConsoleGrammar(Grammar): +# rules = [ +# PatternRule(r'console_mesg', r'^[A-Za-z].*$'), +# PatternRule(r'console_input', r'^>>>.*$'), +# PatternRule(r'console_input', r'^-->.*$'), +# RegionRule(r'string', r'"', StringGrammar, r'"'), +# RegionRule(r'string', r"'", StringGrammar, r"'"), +# #RegionRule(r'console_repr', r"<", ReprGrammar, r">"), +# PatternRule(r'console_reserved', r'True|False|None'), +# PatternRule(r'console_bareword', r'[a-zA-Z_][a-zA-Z0-9_]*'), +# ] +class Console(mode.Fundamental): + modename = 'IPerl' +# grammar = ConsoleGrammar() +# colors = { +# 'console_mesg': ('magenta', 'default', 'bold'), +# 'console_input': ('cyan', 'default', 'bold'), +# 'console_reserved': ('magenta', 'default', 'bold'), +# 'console_bareword': ('default', 'default', 'bold'), +# 'console_repr.start': ('default', 'default', 'bold'), +# 'console_repr.null': ('blue', 'default', 'bold'), +# 'console_repr.string.start': ('cyan', 'default', 'bold'), +# 'console_repr.string.null': ('cyan', 'default', 'bold'), +# 'console_repr.string.end': ('cyan', 'default', 'bold'), +# 'console_repr.end': ('default', 'default', 'bold'), +# } + +install = Console.install diff --git a/mode/iperlmini.py b/mode/iperlmini.py new file mode 100644 index 0000000..6ada584 --- /dev/null +++ b/mode/iperlmini.py @@ -0,0 +1,167 @@ +import code, re, string, StringIO, sys, traceback +import buffer, color, completer, lex, method, mode, mode.mini, mode.consolemini, window +from subprocess import Popen, PIPE, STDOUT +#from lex import Grammar, PatternRule +from mode.perl import PerlGrammar +from point import Point + +PAD = ' ' +LIMIT = 79 + +class IperlExec(method.Method): + def _execute(self, w, **vargs): + s = w.buffer.make_string() + w.mode.history[-1] = s + w.mode.history.append('') + w.buffer.set_data('') + w.mode.hindex = len(w.mode.history) - 1 + + a = w.application + if not a.has_buffer_name('*IPerl*'): + raise Exception, "No iperl found!" + b = a.bufferlist.get_buffer_by_name('*IPerl*') + if a.window().buffer is not b: + a.switch_buffer(b) + p = a.get_mini_buffer_prompt() + b.insert_string(b.get_buffer_end(), p + s + '\n', force=True) + + w.mode.pipe.stdin.write("ENTER:%s\n" % s) + w.mode.pipe.stdin.flush() + output = w.mode._read() + b.insert_string(b.get_buffer_end(), output, force=True) + +class IperlTab(method.Method): + def execute(self, w, **vargs): + a = w.application + s = w.buffer.make_string() + + x = w.logical_cursor().x + if not s or s[:x].isspace(): + w.insert_string_at_cursor(' ' * w.mode.tabwidth) + return + + l = lex.Lexer(w.mode, PerlGrammar) + tokens = list(l.lex([s])) + + t = None + for t2 in tokens: + if t2.x < x and t2.end_x() >= x: + t = t2 + break + if t is None: + return + + s = t.string + w.mode.pipe.stdin.write("COMPLETE:%s\n" % s) + w.mode.pipe.stdin.flush() + (typ_, value) = w.mode._readline() + assert typ_ == 'COMPLETIONS' + + candidates = [x for x in value.split('|') if x] + w.mode._read() + + if candidates: + s = completer.find_common_string(candidates) + w.buffer.delete(Point(t.x, 0), Point(t.end_x(), 0), force=True) + w.insert_string_at_cursor(s) + mode.mini.use_completion_window(a, s, candidates) + +class IperlStart(method.Method): + '''Evaluate python expressions (for advanced use and debugging only)''' + def execute(self, w, **vargs): + a = w.application + if not a.has_buffer_name('*IPerl*'): + b = buffer.IperlBuffer() + a.add_buffer(b) + window.Window(b, a) + b = a.bufferlist.get_buffer_by_name('*IPerl*') + if a.window().buffer is not b: + a.switch_buffer(b) + f = lambda x: None + w.application.open_mini_buffer('*** ', f, self, None, 'iperlmini') + +class IperlPageUp(mode.consolemini.ConsolePageUp): + subbuf = '*IPerl*' +class IperlPageDown(mode.consolemini.ConsolePageDown): + subbuf = '*IPerl*' +class IperlGotoBeginning(mode.consolemini.ConsoleGotoBeginning): + subbuf = '*IPerl*' +class IperlGotoEnd(mode.consolemini.ConsoleGotoEnd): + subbuf = '*IPerl*' + +class IperlMini(mode.Fundamental): + modename = 'IperlMini' + actions = [IperlExec, IperlTab, IperlStart, + IperlPageUp, IperlPageDown, IperlGotoBeginning, IperlGotoEnd] + readre = re.compile('^([A-Z]+):(.*)\n$') + def _readline(self): + line = self.pipe.stdout.readline() + m = self.readre.match(line) + if m: + return (m.group(1), m.group(2)) + else: + return ('RAW', line.rstrip()) + def _read(self): + output = [] + while True: + (type_, value) = self._readline() + if type_ == 'PROMPT': + self.window.application.set_mini_buffer_prompt(value + ' ') + break + elif type_ in ('ERROR', 'RESULT'): + output.append(value.replace('\\n', '\n').replace('\\\\', '\\')) + elif type_ == 'RAW': + output.append(value) + return '\n'.join(output) + '\n' + + def __init__(self, w): + mode.Fundamental.__init__(self, w) + self.bindings = {} + self.history = [''] + self.hindex = 0 + + cmd = ('iperl', '-p') + self.pipe = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + (type_, value) = self._readline() + assert type_ == 'PROMPT', type_ + w.application.set_mini_buffer_prompt('%s ' % value.strip()) + + self.add_bindings('start-of-line', ('C-a', 'HOME',)) + self.add_bindings('end-of-line', ('C-e', 'END',)) + self.add_bindings('backward', ('C-b', 'L_ARROW',)) + self.add_bindings('forward', ('C-f', 'R_ARROW',)) + self.add_bindings('delete-left', ('DELETE', 'BACKSPACE',)) + self.add_bindings('delete-left-word', ('M-DELETE', 'M-BACKSPACE',)) + self.add_bindings('delete-right', ('C-d',)) + self.add_bindings('delete-right-word', ('M-d',)) + self.add_bindings('kill-region', ('C-w',)) + self.add_bindings('copy-region', ('M-w',)) + self.add_bindings('kill', ('C-k',)) + self.add_bindings('copy', ('M-k',)) + self.add_bindings('yank', ('C-y',)) + self.add_bindings('pop-kill', ('M-y',)) + self.add_bindings('right-word', ('M-f',)) + self.add_bindings('left-word', ('M-b',)) + self.add_bindings('set-mark', ('C-@',)) + self.add_bindings('switch-mark', ('C-x C-x',)) + self.add_bindings('undo', ('C-/', 'C-x u',)) + self.add_bindings('redo', ('M-/', 'M-_', 'C-x r',)) + self.add_bindings('toggle-margins', ('M-m',)) + self.add_bindings('transpose-words', ('M-t',)) + self.add_bindings('delete-left-whitespace', ('C-c DELETE', 'C-c BACKSPACE',)) + self.add_bindings('delete-right-whitespace', ('C-c d',)) + self.add_bindings('insert-space', ('SPACE',)) + self.add_bindings('iperl-exec', ('RETURN',)) + self.add_bindings('console-clear', ('C-l',)) + self.add_bindings('console-cancel', ('C-]',)) + self.add_bindings('console-history-prev', ('C-p',)) + self.add_bindings('console-history-next', ('C-n',)) + self.add_bindings('iperl-tab', ('TAB',)) + self.add_bindings('iperl-page-up', ('M-v',)) + self.add_bindings('iperl-page-down', ('C-v',)) + self.add_bindings('iperl-goto-beginning', ('M-<',)) + self.add_bindings('iperl-goto-end', ('M->',)) + for c in string.letters + string.digits + string.punctuation: + self.add_binding('insert-string-%s' % c, c) + +install = IperlMini.install diff --git a/mode/mini.py b/mode/mini.py index 8d9097d..3b605be 100644 --- a/mode/mini.py +++ b/mode/mini.py @@ -1,6 +1,21 @@ import string import buffer, method, mode, window +def use_completion_window(app, s, candidates): + if app.completion_window_is_open(): + w = app.get_completion_window() + if w.buffer._completion != s: + app.close_completion_buffer() + + if app.completion_window_is_open(): + w = app.get_completion_window() + if w.last_is_visible(): + w.goto_beginning() + else: + w.page_down() + elif len(candidates) > 1: + app.open_completion_buffer(s, sorted(candidates)) + class MiniCallback(method.Method): def execute(self, w, **vargs): app = w.application @@ -19,21 +34,8 @@ class MiniTabComplete(method.Method): s2, exists, complete = b.tabber.tab_string(s1, w) b.set_data(s2) - if app.completion_window_is_open(): - w2 = app.get_completion_window() - if w2.buffer._completion != s2: - app.close_completion_buffer() - - if app.completion_window_is_open(): - w2 = app.get_completion_window() - if w2.last_is_visible(): - w2.goto_beginning() - else: - w2.page_down() - else: - candidates = sorted(b.tabber.get_candidates(s1, w)) - if len(candidates) > 1: - app.open_completion_buffer(s2, candidates) + candidates = b.tabber.get_candidates(s1, w) + use_completion_window(app, s2, candidates) class Mini(mode.Fundamental): modename = 'Mini' diff --git a/tools/iperl b/tools/iperl new file mode 100755 index 0000000..654f53f --- /dev/null +++ b/tools/iperl @@ -0,0 +1,273 @@ +#!/usr/bin/perl +# +# by Erik Osheim +# +# licensed under the GNU GPL version 2 + +use Data::Dumper; +use File::Basename; +use Getopt::Long; +use Perl6::Slurp; +use Scalar::Util; +use Term::ReadLine; + +Getopt::Long::Configure('bundling'); + +$| = 1; +$Data::Dumper::Terse = 1; +$Data::Dumper::Indent = 1; + +sub usage { + my($status) = @_; + my $prog = basename($0); + print < sub { usage(0); }, + 'eval|e=s' => sub { push(@preload, ['eval', $_[1]]); }, + 'pipe|p' => sub { $pipe = 'line' }, + 'quiet|q' => sub { $verbose = 0 }, + 'run|r=s' => sub { push(@preload, ['run', $_[1]]); }, + 'use|u=s' => sub { push(@preload, ['use', $_[1]]); }, + 'verbose|v' => sub { $verbose = 'please'; }, +) || usage(1); + +my ($prompt, $input) = (">>>", ""); +unless($pipe) { + print "Welcome to Erik's Perl Interpreter\n"; + print "(Type \"help\", \"exit\", or something else)\n"; +} + +my $pkgregex = qr/^(?:[a-zA-Z_][a-zA-Z0-9_]*::)+$/; +my $nameregex = qr/^[a-zA-Z_][a-zA-Z0-9_]*$/; +sub sigilfind { + my ($pkg, $sigil, $base, $proc) = @_; + $base = "" unless $base; + $proc = {$pkg => 1} unless $proc; + $pkg = $base if $base; + my @results; + foreach my $item (eval("keys(\%${pkg})")) { + my $item2 = $base . $item; + next if $proc->{$item2}; + if($item =~ m/$nameregex/) { + push(@results, $item2) if eval("defined(${sigil}::${base}${item})"); + } elsif($item =~ m/$pkgregex/) { + $proc->{$item2} = 1; + push(@results, sigilfind($item2, $sigil, $item2, $proc)); + } + } + return @results; +} + +sub _repr { + my ($item) = @_; + if(!defined($item)) { + return 'undef'; + } elsif(Scalar::Util::blessed($item)) { + return "$item"; + } elsif(ref($item) eq 'CODE') { + return "$item"; + } else { + my $s = Dumper($item); + $s =~ s/\n+$//; + return $s; + } +} + +sub repr { + my @output; + foreach my $item (@_) { + push(@output, _repr($item)); + } + return join("\n", @output); +} + +sub escape { + my($s) = @_; + $s =~ s/\\/\\\\/g; + $s =~ s/\n/\\n/g; + return $s; +} + +sub _draw { + my ($s, $prefix, $suffix) = @_; + if($pipe) { + print "$prefix:" . escape($s) . "\n"; + } else { + print $s . ($suffix ? $suffix : ''); + } +} + +sub draw_prompt { + my($p) = @_; + _draw($p, "PROMPT", " "); +} +sub draw_result { + my ($result) = @_; + $result = 'undef' unless defined($result); + _draw($result, "RESULT", "\n"); +} +sub draw_error { + my ($err) = @_; + _draw($err, "ERROR"); +} +sub draw_completions { + my (@items) = @_; + if($pipe) { + print 'COMPLETIONS:' . join('|', @items) . "\n"; + } else { + print join("\n", @items) . "\n"; + } +} +sub draw_message { + my ($mesg) = @_; + _draw($mesg, "MESSAGE"); +} +sub draw_exit { + _draw("Bye.", "EXIT", "\n"); +} + +sub complete { + my ($word) = @_; + $word =~ m/^([&\$%@\*]?)(.*)$/; + my $sigil = $1 ? $1 : '&'; + my $name = $2; + my @candidates = grep { + $_ =~ m/^$name/ + } sigilfind('main::', $sigil); + return map { "${1}$_" } sort(@candidates); +} + +my $HELP = <[0] eq 'eval') { + eval $pair->[1]; + die "failed to eval '$pair->[1]': $@" if $@; + } elsif($pair->[0] eq 'run') { + die "no path named $pair->[1] found" unless -e $pair->[1]; + my $data = slurp($pair->[1]); + eval $data; + die "failed to run $pair->[1]: $@" if $@; + } elsif($pair->[0] eq 'use') { + eval "use $pair->[1]"; + die "failed to use $pair->[1]: $@" if $@; + } + } + + unless($pipe) { + $term = Term::ReadLine->new('IPERL'); + my $attribs = $term->Attribs(); + $attribs->{special_prefixes} = '$@%*&'; + $attribs->{completion_function} = sub { + my ($word, $line, $x) = @_; + return complete($word); + }; + } + + while(1) { + my $line; + if($pipe) { + draw_prompt($prompt); + $line = ; + } else { + $line = $term->readline("$prompt "); + } + last unless defined($line); + chomp($line); + + if($pipe) { + if($line =~ m/ENTER:(.*)$/) { + $line = $1; + } else { + if($line =~ m/COMPLETE:(.*)$/) { + draw_completions(complete($1)); + } else { + draw_error("malformed pipe input line"); + } + next; + } + } + + if($line eq '') { + } elsif($line eq 'exit') { + last; + } elsif($line eq 'help') { + ($input, $prompt) = ("", ">>>"); + draw_message($HELP); + } elsif($line eq 'reload') { + ($input, $prompt) = ("", ">>>"); + draw_message("reloading...\n"); + exec($0, @OLDARGV); + } elsif($line eq 'sh') { + ($input, $prompt) = ("", ">>>"); + system("bash") unless $pipe; + } elsif($line =~ m/^sh (.+)$/) { + ($input, $prompt) = ("", ">>>"); + system($1) unless $pipe; + } elsif($line =~ m/^:tab *(.*)$/) { + } elsif($line eq ':code') { + draw_completions(sigilfind('main::', '&')); + } elsif($line eq ':scalar') { + draw_completions(sigilfind('main::', '$')); + } elsif($line eq ':hash') { + draw_completions(sigilfind('main::', '%')); + } elsif($line eq ':array') { + draw_completions(sigilfind('main::', '@')); + } elsif($line eq ':glob') { + draw_completions(sigilfind('main::', '*')); + } elsif($line =~ m/^[\t ]/ || $line =~ m/\{ *$/) { + $input .= $line; + $prompt = "..>"; + } elsif($line =~ m/\\ *$/) { + $line =~ s/\\ *$//; + $input .= $line; + $prompt = "..>"; + } else { + $input .= $line; + my @results = map { chomp($_); repr($_); } eval($input); + if($@) { + draw_error($@); + } elsif(scalar(@results) < 2) { + draw_result($results[0]); + } else { + draw_result(join(", ", @results)); + } + ($input, $prompt) = ("", ">>>"); + } + } + draw_exit(); +} +main();