Perl-LanguageServer
view release on metacpan or search on metacpan
lib/Perl/LanguageServer.pm view on Meta::CPAN
use Moose ;
use Moose::Util qw( apply_all_roles );
use Coro ;
use Coro::AIO ;
use Coro::Handle ;
use AnyEvent;
use AnyEvent::Socket ;
use JSON ;
use Data::Dump qw{dump pp} ;
use IO::Select ;
use Perl::LanguageServer::Req ;
use Perl::LanguageServer::Workspace ;
with 'Perl::LanguageServer::Methods' ;
with 'Perl::LanguageServer::IO' ;
no warnings 'uninitialized' ;
=head1 NAME
Perl::LanguageServer - Language Server and Debug Protocol Adapter for Perl
=head1 VERSION
Version 2.5.0
=cut
our $VERSION = '2.6.2';
=head1 SYNOPSIS
This is a Language Server and Debug Protocol Adapter for Perl
It implements the Language Server Protocol which provides
syntax-checking, symbol search, etc. Perl to various editors, for
example Visual Studio Code or Atom.
L<https://microsoft.github.io/language-server-protocol/specification>
It also implements the Debug Adapter Protocol, which allow debugging
with various editors/includes
L<https://microsoft.github.io/debug-adapter-protocol/overview>
Should work with any Editor/IDE that support the Language-Server-Protocol.
To use both with Visual Studio Code, install the extension "perl"
Any comments and patches are welcome.
=cut
our $json = JSON -> new -> utf8(1) -> ascii(1) ;
our $jsonpretty = JSON -> new -> utf8(1) -> ascii(1) -> pretty (1) ;
our %running_reqs ;
our %running_coros ;
our $exit ;
our $workspace ;
our $dev_tool ;
our $debug1 = 0 ;
our $debug2 = 0 ;
our $log_file ;
our $client_version ;
our $reqseq = 1_000_000_000 ;
has 'channel' =>
(
is => 'ro',
isa => 'Coro::Channel',
default => sub { Coro::Channel -> new }
) ;
has 'debug' =>
(
is => 'rw',
isa => 'Int',
default => 1,
) ;
has 'listen_port' =>
(
is => 'rw',
isa => 'Maybe[Int]',
) ;
has 'roles' =>
(
is => 'rw',
isa => 'HashRef',
default => sub { {} },
) ;
has 'out_semaphore' =>
(
is => 'ro',
isa => 'Coro::Semaphore',
default => sub { Coro::Semaphore -> new }
) ;
has 'log_prefix' =>
(
is => 'rw',
isa => 'Str',
default => 'LS',
) ;
has 'log_req_txt' =>
(
is => 'rw',
isa => 'Str',
default => '---> Request: ',
) ;
# ---------------------------------------------------------------------------
lib/Perl/LanguageServer.pm view on Meta::CPAN
my $name ;
if ($method =~ /^(\w+)\/(\w+)$/)
{
$module = $1 ;
$name = $2 ;
}
elsif ($method =~ /^(\w+)$/)
{
$name = $1 ;
}
elsif ($method =~ /^\$\/(\w+)$/)
{
$name = $1 ;
}
else
{
die "Unknown method $method" ;
}
$module = $req -> type eq 'dbgint'?'DebugAdapterInterface':'DebugAdapter' if ($req -> is_dap) ;
my $base_package = __PACKAGE__ . '::Methods' ;
my $package = $base_package ;
$package .= '::' . $module if ($module) ;
my $fn = $package . '.pm' ;
$fn =~ s/::/\//g ;
if (!exists $INC{$fn} || !exists $self -> roles -> {$fn})
{
#$self -> logger (dump (\%INC), "\n") ;
$self -> logger ("apply_all_roles ($self, $package, $fn)\n") ;
apply_all_roles ($self, $package) ;
$self -> roles -> {$fn} = 1 ;
}
my $perlmethod ;
if ($req -> is_dap)
{
$perlmethod = '_dapreq_' . $name ;
}
else
{
$perlmethod = (defined($id)?'_rpcreq_':'_rpcnot_') . $name ;
}
$self -> logger ("method=$perlmethod\n") if ($debug1) ;
die "Unknown perlmethod $perlmethod" if (!$self -> can ($perlmethod)) ;
no strict ;
return $self -> $perlmethod ($workspace, $req) ;
use strict ;
}
# ---------------------------------------------------------------------------
sub process_req
{
my ($self, $id, $reqdata) = @_ ;
my $xid = $id ;
$xid ||= $reqseq++ ;
$running_coros{$xid} = async
{
my $req_guard = Guard::guard
{
$self -> logger ("done handle_req id=$xid\n") if ($debug1) ;
delete $running_reqs{$xid} ;
delete $running_coros{$xid} ;
};
my $type = $reqdata -> {type} ;
my $is_dap = $type?1:0 ;
$type = defined ($id)?'request':'notification' if (!$type) ;
$self -> logger ("handle_req id=$id\n") if ($debug1) ;
my $req = Perl::LanguageServer::Req -> new ({ id => $id, is_dap => $is_dap, type => $type, params => $is_dap?$reqdata -> {arguments} || {}:$reqdata -> {params} || {}}) ;
$running_reqs{$xid} = $req ;
my $rsp ;
my $outdata ;
my $outjson ;
eval
{
$rsp = $self -> call_method ($reqdata, $req, $id) ;
$id = undef if (!$rsp) ;
if ($req -> is_dap)
{
$outjson = { request_seq => -$id, seq => -$id, command => $reqdata -> {command}, success => JSON::true, type => 'response', $rsp?(body => $rsp):()} ;
}
else
{
$outjson = { id => $id, jsonrpc => '2.0', result => $rsp} if ($rsp) ;
}
$outdata = $json -> encode ($outjson) if ($outjson) ;
} ;
if ($@)
{
$self -> logger ("ERROR: $@\n") ;
if ($req -> is_dap)
{
$outjson = { request_seq => -$id, command => $reqdata -> {command}, success => JSON::false, message => "$@", , type => 'response'} ;
}
else
{
$outjson = { id => $id, jsonrpc => '2.0', error => { code => -32001, message => "$@" }} ;
}
$outdata = $json -> encode ($outjson) if ($outjson) ;
}
if (defined($id))
{
my $guard = $self -> out_semaphore -> guard ;
use bytes ;
my $len = length ($outdata) ;
my $wrdata = "Content-Length: $len\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n$outdata" ;
my $sum = 0 ;
my $cnt ;
while ($sum < length ($wrdata))
{
$cnt = $self -> _write ($wrdata, undef, $sum) ;
die "write_error ($!)" if ($cnt <= 0) ;
$sum += $cnt ;
}
if ($debug1)
{
$wrdata =~ s/\r//g ;
$self -> logger ("<--- Response: ", $jsonpretty -> encode ($outjson), "\n") ;
}
lib/Perl/LanguageServer.pm view on Meta::CPAN
Coro::AnyEvent::sleep (2) ;
IO::AIO::reinit () ; # stop AIO requests
exit (1) ; # stop LS, vscode will restart it
}
}
}
}
# ---------------------------------------------------------------------------
sub run
{
my $listen_port ;
my $no_stdio ;
my $heartbeat ;
while (my $opt = shift @ARGV)
{
if ($opt eq '--debug')
{
$debug1 = $debug2 = 1 ;
}
elsif ($opt eq '--log-level')
{
$debug1 = shift @ARGV ;
$debug2 = $debug1 > 1?1:0 ;
}
elsif ($opt eq '--log-file')
{
$log_file = shift @ARGV ;
}
elsif ($opt eq '--port')
{
$listen_port = shift @ARGV ;
}
elsif ($opt eq '--nostdio')
{
$no_stdio = 1 ;
}
elsif ($opt eq '--heartbeat')
{
$heartbeat = 1 ;
}
elsif ($opt eq '--version')
{
$client_version = shift @ARGV ;
}
}
$|= 1 ;
my $cv = AnyEvent::CondVar -> new ;
async
{
my $i = 0 ;
while (1)
{
if ($heartbeat || $debug2)
{
logger (undef, "##### $i #####\n running: " . dump (\%running_reqs) . " coros: " . dump (\%running_coros), "\n") ;
$i++ ;
}
Coro::AnyEvent::sleep (10) ;
}
} ;
if (!$no_stdio)
{
async
{
my $self = Perl::LanguageServer -> new ({out_fh => 1, in_fh => 0});
$self -> listen_port ($listen_port) ;
$self -> mainloop () ;
$cv -> send ;
} ;
}
async
{
_run_tcp_server ($listen_port) ;
} ;
$cv -> recv ;
$exit = 1 ;
}
# ---------------------------------------------------------------------------
sub parsews
{
my $class = shift ;
my @args = @_ ;
$|= 1 ;
my $cv = AnyEvent::CondVar -> new ;
async
{
my $self = Perl::LanguageServer -> new ;
$workspace = Perl::LanguageServer::Workspace -> new ({ config => {} }) ;
my %folders ;
foreach my $path (@args)
{
$folders{$path} = $path ;
}
$workspace -> folders (\%folders) ;
$workspace -> background_parser ($self) ;
$cv -> send ;
} ;
$cv -> recv ;
}
# ---------------------------------------------------------------------------
lib/Perl/LanguageServer.pm view on Meta::CPAN
{
foreach my $path (@args)
{
my $text ;
aio_load ($path, $text) ;
$workspace -> check_perl_syntax ($workspace, $path, $text) ;
}
} ;
$cv -> recv ;
}
1 ;
__END__
=head1 DOCUMENTATION
Language Server and Debug Protocol Adapter for Perl
=head2 Features
=over
=item * Language Server
=over
=item * Syntax checking
=item * Symbols in file
=item * Symbols in workspace/directory
=item * Goto Definition
=item * Find References
=item * Call Signatures
=item * Supports multiple workspace folders
=item * Document and selection formatting via perltidy
=item * Run on remote system via ssh
=item * Run inside docker container
=item * Run inside kubernetes
=back
=item * Debugger
=over
=item * Run, pause, step, next, return
=item * Support for coro threads
=item * Breakpoints
=item * Conditional breakpoints
=item * Breakpoints can be set while program runs and for modules not yet loaded
=item * Variable view, can switch to every stack frame or coro thread
=item * Set variable
=item * Watch variable
=item * Tooltips with variable values
=item * Evaluate perl code in debuggee, in context of every stack frame of coro thread
=item * Automatically reload changed Perl modules while debugging
=item * Debug multiple perl programs at once
=item * Run on remote system via ssh
=item * Run inside docker container
=item * Run inside kubernetes
=back
=back
=head2 Requirements
You need to install the perl module Perl::LanguageServer to make this extension work,
e.g. run C<cpan Perl::LanguageServer> on your target system.
Please make sure to always run the newest version of Perl::LanguageServer as well.
NOTE: Perl::LanguageServer depend on AnyEvent::AIO and Coro. There is a warning that
this might not work with newer Perls. It works fine for Perl::LanguageServer. So just
confirm the warning and install it.
Perl::LanguageServer depends on other Perl modules. It is a good idea to install most
of then with your linux package manager.
e.g. on Debian/Ubuntu run:
sudo apt install libanyevent-perl libclass-refresh-perl libcompiler-lexer-perl \
libdata-dump-perl libio-aio-perl libjson-perl libmoose-perl libpadwalker-perl \
libscalar-list-utils-perl libcoro-perl
sudo cpan Perl::LanguageServer
e.g. on Centos 7 run:
sudo yum install perl-App-cpanminus perl-AnyEvent-AIO perl-Coro
sudo cpanm Class::Refresh
sudo cpanm Compiler::Lexer
sudo cpanm Hash::SafeKeys
sudo cpanm Perl::LanguageServer
In case any of the above packages are not available for your os version, just
leave them out. The cpan command will install missing dependencies. In case
the test fails, when running cpan C<install>, you should try to run C<force install>.
=head2 Extension Settings
This extension contributes the following settings:
=over
=item * C<perl.enable>: enable/disable this extension
=item * C<perl.sshAddr>: ip address of remote system
=item * C<perl.sshPort>: optional, port for ssh to remote system
=item * C<perl.sshUser>: user for ssh login
=item * C<perl.sshCmd>: defaults to ssh on unix and plink on windows
=item * C<perl.sshWorkspaceRoot>: path of the workspace root on remote system
=item * C<perl.perlCmd>: defaults to perl
=item * C<perl.perlArgs>: additional arguments passed to the perl interpreter that starts the LanguageServer
=item * C<useTaintForSyntaxCheck>: if true, use taint mode for syntax check
=item * C<perl.sshArgs>: optional arguments for ssh
=item * C<perl.pathMap>: mapping of local to remote paths
=item * C<perl.perlInc>: array with paths to add to perl library path. This setting is used by the syntax checker and for the debuggee and also for the LanguageServer itself.
=item * C<perl.fileFilter>: array for filtering perl file, defaults to [I<.pm,>.pl]
=item * C<perl.ignoreDirs>: directories to ignore, defaults to [.vscode, .git, .svn]
=item * C<perl.debugAdapterPort>: port to use for connection between vscode and debug adapter inside Perl::LanguageServer.
=item * C<perl.debugAdapterPortRange>: if debugAdapterPort is in use try ports from debugAdapterPort to debugAdapterPort + debugAdapterPortRange. Default 100.
=item * C<perl.showLocalVars>: if true, show also local variables in symbol view
=item * C<perl.logLevel>: Log level 0-2.
lib/Perl/LanguageServer.pm view on Meta::CPAN
=item * Fix: Typos and spelling in README (#159) [dseynhae]
=item * Fix: Update call to gensym(), to fix 'strict subs' error (#164) [KohaAloha]
=item * Convert identention from tabs to spaces and remove trailing whitespaces
=back
=head2 2.4.0 C<2022-11-18>
=over
=item * Choose a different port for debugAdapterPort if it is already in use. This
avoids trouble with starting C<Perl::LanguageServer> if another instance
of C<Perl::LanguageServer> is running on the same machine (thanks to hakonhagland)
=item * Add configuration C<debugAdapterPortRange>, for choosing range of port for dynamic
port assignment
=item * Add support for using LanguageServer and debugger inside a Container.
Currently docker containers und containers running inside kubernetes are supported.
=item * When starting debugger session and C<stopOnEntry> is false, do not switch to sourefile
where debugger would stop, when C<stopOnEntry> is true.
=item * Added some FAQs in README
=item * Fix: Debugger stopps at random locations
=item * Fix: debugAdapterPort is now numeric
=item * Fix: debugging loop with each statement (#107)
=item * Fix: display of arrays in variables pane on mac (#120)
=item * Fix: encoding for C<perltidy> (#127)
=item * Fix: return error if C<perltidy> fails, so text is not removed by failing
formatting request (#87)
=item * Fix: FindBin does not work when checking syntax (#16)
=back
=head2 2.3.0 C<2021-09-26>
=over
=item * Arguments section in Variable lists now C<@ARGV> and C<@_> during debugging (#105)
=item * C<@_> is now correctly evaluated inside of debugger console
=item * C<$#foo> is now correctly evaluated inside of debugger console
=item * Default debug configuration is now automatically provided without
the need to create a C<launch.json> first (#103)
=item * Add Option C<cacheDir> to specify location of cache dir (#113)
=item * Fix: Debugger outputted invalid thread reference causes "no such coroutine" message,
so watchs and code from the debug console is not expanded properly
=item * Fix: LanguageServer hangs when multiple request send at once from VSCode to LanguageServer
=item * Fix: cwd parameter for debugger in launch.json had no effect (#99)
=item * Fix: Correctly handle paths with drive letters on windows
=item * Fix: sshArgs parameter was not declared as array (#109)
=item * Disable syntax check on windows, because it blocks the whole process when running on windows,
until handling of child's processes is fixed
=item * Fixed spelling (#86,#96,#101) [chrstphrchvz,davorg,aluaces]
=back
=head2 2.2.0 C<2021-02-21>
=over
=item * Parser now supports Moose method modifieres before, after and around,
so they can be used in symbol view and within reference search
=item * Support Format Document and Format Selection via perltidy
=item * Add logFile config option
=item * Add perlArgs config option to pass options to Perl interpreter. Add some documentation for config options.
=item * Add disableCache config option to make LanguageServer usable with readonly directories.
=item * updated dependencies package.json & package-lock.json
=item * Fix deep recursion in SymbolView/Parser which was caused by function prototypes.
Solves also #65
=item * Fix duplicate req id's that caused cleanup of still
running threads which in turn caused the LanguageServer to hang
=item * Prevent dereferencing an undefined value (#63) [Heiko Jansen]
=item * Fix datatype of cwd config options (#47)
=item * Use perlInc setting also for LanguageServer itself (based only pull request #54 from ALANVF)
=item * Catch Exceptions during display of variables inside debugger
=item * Fix detecting duplicate LanguageServer processes
=item * Fix spelling in documentation (#56) [Christopher Chavez]
=item * Remove notice about Compiler::Lexer 0.22 bugs (#55) [Christopher Chavez]
=item * README: Typo and grammar fixes. Add Carton lib path instructions. (#40) [szTheory]
=item * README: Markdown code block formatting (#42) [szTheory]
=item * Makefile.PL: add META_MERGE with GitHub info (#32) [Christopher Chavez]
( run in 0.557 second using v1.01-cache-2.11-cpan-e1769b4cff6 )