App-Chart
view release on metacpan or search on metacpan
lib/App/Chart/EmacsMain.pm view on Meta::CPAN
# Copyright 2008, 2009, 2010, 2011, 2014, 2015, 2016, 2017 Kevin Ryde
# This file is part of Chart.
#
# Chart is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3, or (at your option) any later version.
#
# Chart is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with Chart. If not, see <http://www.gnu.org/licenses/>.
# ENHANCE-ME:
# remember which latests sent, only give 'update' for them
package App::Chart::EmacsMain;
use 5.010;
use strict;
use warnings;
use Encode;
use Encode::Locale; # for coding system "locale"
use IO::Handle;
use Lisp::Reader;
use Lisp::Printer ('lisp_print');
use Lisp::Symbol ('symbol');
use POSIX ();
use Regexp::Common 'whitespace';
use Locale::TextDomain 'App-Chart';
use App::Chart;
# set this to 1 for development debugging prints
use constant { DEBUG => 0,
DEBUG_TTY_FILENAME => '/dev/tty7' };
use constant PROTOCOL_VERSION => 102;
my $emacs_fh;
# reopen $fd on $filename with $omode a POSIX::O_WRONLY() etc value
# return $fd on success, or undef with $! set on error
sub fdreopen {
my ($fd, $filename, $omode) = @_;
my $file_fd = POSIX::open ($filename, $omode);
if (! defined $fd) { return undef; }
# print "fdreopen $fd $filename, via $file_fd\n";
if (! defined POSIX::dup2 ($file_fd, $fd)) {
{
local $!;
POSIX::close ($file_fd);
}
return undef;
}
if (! defined POSIX::close ($file_fd)) { return undef; }
return $fd;
}
sub main {
my ($class) = @_;
# subprocess unbuffered and utf8
binmode (STDIN, ':utf8') or die;
# dup-ed to a new descriptor to talk to emacs
open $emacs_fh, '>&STDOUT' or die;
$emacs_fh->autoflush(1); # emacs_write() does single-string prints
# ENHANCE-ME: use one of the IO::Capture or via layer or whatnot to get
# perl prints to STDOUT/STDERR and send them up to an emacs buffer, or
# message area
#
# stdout/stderr fds 1 and 2 put to /dev/null to discard other prints
my $devnull = File::Spec->devnull;
fdreopen (1, $devnull, POSIX::O_WRONLY())
// die "Cannot send STDOUT to $devnull: ", Glib::strerror($!);
POSIX::dup2 (1, 2) // die;
if (DEBUG) {
# fds 1 and 2 changed (again) to DEBUG_TTY_FILENAME, if it's possible to
# open that
if (fdreopen (1, DEBUG_TTY_FILENAME, POSIX::O_WRONLY())) {
POSIX::dup2 (1, 2) // die "Cannot dup fd 1 to fd 2: $!";
print "EmacsMain started, emacs_fh fd=",fileno($emacs_fh),
", diagnostics on ",DEBUG_TTY_FILENAME,"\n";
print " STDOUT fd ",fileno(STDOUT),", STDERR fd ",fileno(STDERR),"\n";
STDOUT->autoflush(1);
STDERR->autoflush(1); # probably true already
}
}
# initial message
emacs_write (symbol('init'), 'UTF-8', PROTOCOL_VERSION);
my $mainloop = Glib::MainLoop->new;
STDIN->blocking(0);
Glib::IO->add_watch (fileno(STDIN), ['in', 'hup', 'err'], \&_do_read,
$mainloop);
$Lisp::Reader::SYMBOLS_AS_STRINGS = 1;
my $dirb = App::Chart::chart_dirbroadcast();
$dirb->listen;
$dirb->connect ('delete-symbol', \&completions_update);
$dirb->connect ('symlist-content-changed', \&_do_symlist_content_changed);
$dirb->connect ('symlist-content-inserted', \&_do_symlist_content_inserted);
$dirb->connect ('symlist-content-deleted', \&_do_symlist_content_deleted);
$dirb->connect ('symlist-content-reordered',\&_do_symlist_content_reordered);
$dirb->connect ('symlist-list-changed', \&_do_symlist_list_changed);
$dirb->connect ('latest-changed', \&send_update);
$dirb->connect ('data-changed', \&send_update);
Glib->install_exception_handler (\&exception_handler);
## no critic (RequireLocalizedPunctuationVars)
$SIG{'__WARN__'} = \&exception_handler;
$mainloop->run;
}
# 'data-changed' and 'latest-changed'
sub send_update {
my ($changed) = @_;
if (DEBUG) { print "Changed: ",join(' ',keys %$changed),"\n"; }
emacs_write (symbol('update'), [ keys %$changed ]);
# this is a bit excessive, only really want to know if new symbols have
# been added to the latest quotes
completions_update();
}
sub exception_handler {
my ($msg) = @_;
# perhaps some modules like LWP will put through a locale $! or similar
unless (utf8::is_utf8($msg)) { $msg = Encode::decode('locale',$msg); }
if (DEBUG) { print "Error ", $msg; }
$msg =~ s/$RE{ws}{crop}//g; # leading and trailing whitespace
# $trace->as_string has non-ascii de-fanged to something printable, so it
# can go straight out
#
my $backtrace;
if (eval { require Devel::StackTrace; }) {
$backtrace = Devel::StackTrace->new->as_string;
$msg .= __('See *chart-process-backtrace* buffer');
}
emacs_write (symbol('error'), $msg, $backtrace);
return 1; # stay installed
}
sub emacs_write {
if (DEBUG >= 2) { require Data::Dumper;
print Data::Dumper::Dumper(\@_); }
if (DEBUG) { print "To emacs: ",lisp_print([@_]),"\n"; }
print $emacs_fh lisp_print([@_]),"\n";
}
my $buf = '';
sub _do_read {
my ($fd, $conditions, $mainloop) = @_;
for (;;) {
if (DEBUG >= 2) { print " read more at ",length($buf),"\n"; }
my $got = read STDIN, $buf, 8192, length($buf);
if (DEBUG >= 2) { print " got ",$got//'undef'," $!\n"; }
if (! defined $got) {
if ($! == POSIX::EWOULDBLOCK()) { last; } # no more data for now
if ($!) {
print STDERR "Read error: ",Glib::strerror($!),"\n";
}
$mainloop->quit;
return 0;
}
if ($got == 0) {
## no critic (ProhibitExit, ProhibitExitInSubroutines)
exit 0;
}
}
my ($aref, $endpos) = Lisp::Reader::lisp_read ($buf);
$buf = substr ($buf, $endpos);
if (DEBUG) { require Data::Dumper;
print "Receive: ",Data::Dumper::Dumper($aref);
print "leaving buf ",length($buf),"\n"; }
foreach my $list (@$aref) {
call_command ($list);
}
return 1; # stay connected
}
sub call_command {
( run in 0.967 second using v1.01-cache-2.11-cpan-ceb78f64989 )