├── log └── .gitkeep ├── author └── monastery-fatpack ├── minil.toml ├── t └── 00-compile.t ├── .travis.yml ├── .editorconfig ├── Changes ├── Build.PL ├── .gitignore ├── cpanfile ├── script └── monastery ├── README.md ├── META.json ├── lib └── App │ ├── Monastery │ ├── Document.pm │ └── Handler.pm │ └── Monastery.pm └── LICENSE /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /author/monastery-fatpack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccakes/p5-app-monastery/HEAD/author/monastery-fatpack -------------------------------------------------------------------------------- /minil.toml: -------------------------------------------------------------------------------- 1 | name = "App-Monastery" 2 | # badges = ["travis"] 3 | module_maker="ModuleBuildTiny" 4 | 5 | [release] 6 | do_not_upload_to_cpan=true 7 | -------------------------------------------------------------------------------- /t/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More 0.98; 3 | 4 | use_ok $_ for qw( 5 | App::Monastery 6 | ); 7 | 8 | done_testing; 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | sudo: false 3 | perl: 4 | - "5.12" 5 | - "5.14" 6 | - "5.16" 7 | - "5.18" 8 | - "5.20" 9 | - "5.22" 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = True 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{pl,pm,t}] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.sql] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [Makefile] 16 | indent_style = tab 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension App-Monastery 2 | 3 | {{$NEXT}} 4 | 5 | 0.02 2017-05-12T06:12:03Z 6 | 7 | - Minor documentation cleanup 8 | 9 | 0.01 2017-05-11T23:23:52Z 10 | 11 | - JSON-RPC via STDIN/STDOUT 12 | - Scans @INC + project directory for symbols to use for completion 13 | - Provides completion via RPC 14 | 15 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 3 | # DO NOT EDIT DIRECTLY. 4 | # ========================================================================= 5 | 6 | use 5.008_001; 7 | use strict; 8 | 9 | use Module::Build::Tiny 0.035; 10 | 11 | Build_PL(); 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build/ 2 | /_build/ 3 | /Build 4 | /Build.bat 5 | /blib 6 | /Makefile 7 | /pm_to_blib 8 | 9 | /carton.lock 10 | /.carton/ 11 | /local/ 12 | 13 | nytprof.out 14 | nytprof/ 15 | 16 | cover_db/ 17 | 18 | *.bak 19 | *.old 20 | *~ 21 | *.swp 22 | *.o 23 | *.obj 24 | 25 | !LICENSE 26 | 27 | /_build_params 28 | 29 | MYMETA.* 30 | 31 | /PPIx-PLS-* 32 | 33 | pls.db 34 | cpanfile.snapshot 35 | log/*.log 36 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl', '5.10.0'; 2 | 3 | requires 'JSON'; 4 | requires 'JSON::RPC::Spec'; 5 | 6 | requires 'PPI'; 7 | requires 'Perl::Tidy'; 8 | requires 'Perl::Critic'; 9 | 10 | requires 'AnyEvent'; 11 | requires 'DBM::Deep'; 12 | requires 'Class::Tiny'; 13 | requires 'Getopt::Long'; 14 | requires 'File::Find::Rule'; 15 | requires 'Number::Bytes::Human'; 16 | 17 | on 'test' => sub { 18 | requires 'Test::More', '0.98'; 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /script/monastery: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # vim: set ft=perl: 3 | 4 | use feature ':5.10'; 5 | use strict; 6 | use warnings; 7 | 8 | our $VERSION = '0.02'; 9 | 10 | use App::Monastery; 11 | App::Monastery->run; 12 | 13 | __END__ 14 | 15 | =encoding utf-8 16 | 17 | =head1 NAME 18 | 19 | monastery - Perl Language Server 20 | 21 | =head1 DESCRIPTION 22 | 23 | This is the script interface to L. Configure your 24 | editor to point here. Most documentation is available at the module 25 | however the script takes a single argument. 26 | 27 | =head1 SYNOPSIS 28 | 29 | monastery [options] 30 | 31 | --log LEVEL Uses L, logs to /tmp/perl-lang-server.log 32 | 33 | =cut 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | App::Monastery - Perl Language Server 4 | 5 | # DESCRIPTION 6 | 7 | App::Monastery is a language server based on the [Language Server Protocol](https://github.com/Microsoft/language-server-protocol/). 8 | Currently Monastery supports a subset of v3.0 of the protocol. 9 | 10 | More can be read about what a Language Server is for at [http://langserver.org](http://langserver.org) 11 | but the idea is to allow editors/IDEs to support a variety of languages 12 | without needing to reimplement lang-specific features themselves or, 13 | as in most cases, not implementing them at all. 14 | 15 | This server is very much **alpha quality** but it has some mildly 16 | intelligent completion options and should be easily expanded with 17 | [Perl::Tidy](https://metacpan.org/pod/Perl::Tidy) to handle formatting and `perl -c` or [Perl::Critic](https://metacpan.org/pod/Perl::Critic) 18 | to provide more useful diagnostics to the user. 19 | 20 | # INSTALLATION 21 | 22 | This can be installed from the repo directly or for convenience, there 23 | is a fatpacked version in author/. 24 | 25 | # install using cpanm 26 | cpanm -i git@github.com:ccakes/p5-app-monastery.git 27 | 28 | # or download fatpacked script 29 | curl -sL -o /usr/local/bin/monastery-fatpack https://raw.githubusercontent.com/ccakes/p5-app-monastery/master/author/monastery-fatpack 30 | 31 | # TODO 32 | 33 | - **Handler.pm** 34 | 35 | This file is a mess, the `$rpc->register` calls need to be abstracted, 36 | ideally into a controller-type model to make adding functionality 37 | simpler. 38 | 39 | - **textDocument/publishDiagnostics** 40 | 41 | Need to work out when best to trigger this, probably on 42 | `textDocument/willSave`. `didChange` would be nicer but a bit 43 | spammy. This should be `perl -c` rather as it's syntax errors. 44 | 45 | - **textDocument/rangeFormatting** 46 | - **workspace/symbol + textDocument/documentSymbol** 47 | - **Some tests...** 48 | 49 | # LICENSE 50 | 51 | Copyright (C) Cameron Daniel. 52 | 53 | This library is free software; you can redistribute it and/or modify 54 | it under the same terms as Perl itself. 55 | 56 | # AUTHOR 57 | 58 | Cameron Daniel 59 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "Perl Language Server", 3 | "author" : [ 4 | "Cameron Daniel " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Minilla/v3.0.10, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : "2" 14 | }, 15 | "name" : "App-Monastery", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples", 24 | "author", 25 | "builder" 26 | ] 27 | }, 28 | "prereqs" : { 29 | "configure" : { 30 | "requires" : { 31 | "Module::Build::Tiny" : "0.035" 32 | } 33 | }, 34 | "develop" : { 35 | "requires" : { 36 | "Test::CPAN::Meta" : "0", 37 | "Test::MinimumVersion::Fast" : "0.04", 38 | "Test::PAUSE::Permissions" : "0.04", 39 | "Test::Pod" : "1.41", 40 | "Test::Spellunker" : "v0.2.7" 41 | } 42 | }, 43 | "runtime" : { 44 | "requires" : { 45 | "AnyEvent" : "0", 46 | "Class::Tiny" : "0", 47 | "DBM::Deep" : "0", 48 | "File::Find::Rule" : "0", 49 | "Getopt::Long" : "0", 50 | "JSON" : "0", 51 | "JSON::RPC::Spec" : "0", 52 | "Number::Bytes::Human" : "0", 53 | "PPI" : "0", 54 | "Perl::Critic" : "0", 55 | "Perl::Tidy" : "0", 56 | "perl" : "v5.10.0" 57 | } 58 | }, 59 | "test" : { 60 | "requires" : { 61 | "Test::More" : "0.98" 62 | } 63 | } 64 | }, 65 | "release_status" : "unstable", 66 | "resources" : { 67 | "bugtracker" : { 68 | "web" : "https://github.com/ccakes/p5-app-monastery/issues" 69 | }, 70 | "homepage" : "https://github.com/ccakes/p5-app-monastery", 71 | "repository" : { 72 | "type" : "git", 73 | "url" : "git://github.com/ccakes/p5-app-monastery.git", 74 | "web" : "https://github.com/ccakes/p5-app-monastery" 75 | } 76 | }, 77 | "version" : "0.02", 78 | "x_serialization_backend" : "JSON::PP version 2.27400_02" 79 | } 80 | -------------------------------------------------------------------------------- /lib/App/Monastery/Document.pm: -------------------------------------------------------------------------------- 1 | package App::Monastery::Document; 2 | 3 | use feature ':5.10'; 4 | use strict; 5 | use warnings; 6 | 7 | use PPI; 8 | use List::Util qw(first sum); 9 | 10 | use Class::Tiny qw(file buf is_package _doc _mtime); 11 | 12 | sub BUILD { 13 | my ($self, $args) = @_; 14 | return unless $args->{file} or $args->{buf}; 15 | 16 | if ($args->{file}) { 17 | return unless -f $args->{file}; 18 | 19 | $self->file( $args->{file} ); 20 | $self->_mtime( (stat $args->{file})[9] ); 21 | $self->_doc( PPI::Document->new($self->file) ); 22 | } 23 | else { 24 | $self->_doc( PPI::Document->new(\$self->buf) ); 25 | } 26 | 27 | $self->is_package( $self->_doc->find_any('PPI::Statement::Package') ); 28 | } 29 | 30 | sub package { 31 | my ($self) = @_; 32 | 33 | my $package = $self->_doc->find_first('PPI::Statement::Package'); 34 | return $package ? $package->namespace : undef; 35 | } 36 | 37 | sub parents { 38 | my ($self) = @_; 39 | 40 | my $parents = $self->_doc->find( 41 | sub { 42 | return 0 unless $_[1]->isa('PPI::Statement'); # only look at Statements, not POD! 43 | 44 | #### 45 | # Find any use base/parent/Mojo::Base or Moose-style extends inheritance 46 | return 1 if $_[1]->isa('PPI::Statement::Include') && $_[1]->content =~ /use (parent|base|['"]?Mojo::Base['"]?) /; 47 | return 1 if ($_[1]->children)[0]->isa('PPI::Token::Word') and ($_[1]->children)[0]->content eq 'extends'; 48 | 49 | return 0; 50 | } 51 | ); 52 | 53 | return [] unless $parents; # no results 54 | 55 | my %list; 56 | foreach my $parent (@{$parents}) { 57 | my $mod = first { $_->isa('PPI::Token::Quote') || $_->isa('PPI::Token::QuoteLike') } reverse $parent->children; 58 | next unless $mod; 59 | 60 | #### 61 | # Parse out name based on quote type 62 | if ($mod->isa('PPI::Token::Quote')) { 63 | my $name = $mod->content; 64 | $name =~ s/['"]//g; 65 | 66 | $list{$name} = undef; 67 | } 68 | elsif ($mod->isa('PPI::Token::QuoteLike')) { 69 | $list{$_} = undef foreach $mod->literal; 70 | } 71 | } 72 | 73 | my @par = keys %list; 74 | return \@par; 75 | } 76 | 77 | sub methods { 78 | my ($self) = @_; 79 | 80 | my $subs = $self->_doc->find('PPI::Statement::Sub'); 81 | return [] unless $subs; 82 | 83 | my %list; 84 | foreach my $sub (@{$subs}) { 85 | next if $sub->name =~ /(BUILD|BUILDARGS|DESTROY|AUTOLOAD)/; 86 | 87 | $list{ $sub->name } = { 88 | label => $sub->name, 89 | #kind => $self->is_package ? 2 : 3, # method vs function 90 | }; 91 | } 92 | 93 | my @methods = values %list; 94 | return \@methods; 95 | } 96 | 97 | sub tokens { 98 | my ($self) = @_; 99 | 100 | my $tokens = $self->_doc->find('PPI::Statement::Variable'); 101 | return [] unless $tokens; 102 | 103 | my %list; 104 | foreach my $token (@{$tokens}) { 105 | my $word = $token->find_first('PPI::Token::Word'); 106 | my $symbol = $token->find_first('PPI::Token::Symbol'); 107 | 108 | next unless $word; # no strict, too hard to tell 109 | next if $self->is_package and $word->content eq 'my'; # only our in packages 110 | 111 | $list{ $symbol->content } = { 112 | label => $symbol->content, 113 | #kind => $self->is_package ? 5 : 6, # field vs variable 114 | }; 115 | } 116 | 117 | my @symbols = values %list; 118 | return \@symbols; 119 | } 120 | 121 | sub object_instances { 122 | my ($self) = @_; 123 | 124 | my $vars= $self->_doc->find('PPI::Statement::Variable'); 125 | return [] unless $vars; 126 | 127 | my %list; 128 | foreach my $var (@{$vars}) { 129 | if ($var->content =~ /([\w:]+)->new/ || $var->content =~ /new ([\w:]+)/) { 130 | my $inst = $var->find_first('PPI::Token::Symbol'); 131 | $list{ $inst->content } = $1; 132 | } 133 | } 134 | 135 | if ($self->is_package) { 136 | $list{'$self'} = $self->package; 137 | } 138 | 139 | return \%list; 140 | } 141 | 142 | sub record { 143 | my ($self) = @_; 144 | 145 | return unless $self->is_package; 146 | 147 | return { 148 | _file => $self->file, 149 | _mtime => $self->_mtime, 150 | 151 | parents => $self->parents, 152 | methods => $self->methods, 153 | tokens => $self->tokens 154 | }; 155 | } 156 | 157 | 1; 158 | -------------------------------------------------------------------------------- /lib/App/Monastery.pm: -------------------------------------------------------------------------------- 1 | package App::Monastery; 2 | 3 | use feature ':5.10'; 4 | use strict; 5 | use warnings; 6 | 7 | our $VERSION = '0.02'; 8 | 9 | use AE; 10 | use AnyEvent::Log; 11 | use AnyEvent::Handle; 12 | 13 | use JSON; 14 | use Encode; 15 | use FindBin; 16 | use File::Spec; 17 | use Getopt::Long; 18 | 19 | use IO::Handle; 20 | STDOUT->autoflush(1); 21 | 22 | use Time::HiRes (); 23 | use constant START => Time::HiRes::time; 24 | 25 | use App::Monastery::Handler; 26 | 27 | sub run { 28 | my %options = (log => 'warn'); 29 | Getopt::Long::GetOptions( 30 | \%options, 31 | 'log|l' 32 | ); 33 | 34 | ###### 35 | # Setup AE::log format 36 | my $INIT = Time::HiRes::time; 37 | AnyEvent::Log::ctx->fmt_cb(sub { 38 | my $ts = sprintf '%.03f s', (Time::HiRes::time - $INIT); 39 | my @res; 40 | 41 | for (split /\n/, sprintf "%-5s %s: %s", $AnyEvent::Log::LEVEL2STR[$_[2]], $_[1][0], $_[3]) { 42 | push @res, "$ts [$$] $_\n"; 43 | } 44 | 45 | join '', @res; 46 | }); 47 | 48 | AnyEvent::Log::ctx->level( $options{log} ); 49 | AnyEvent::Log::ctx->log_to_file('/tmp/perl-lang-server.log'); 50 | 51 | my $handler = App::Monastery::Handler->new(logger => AnyEvent::Log::ctx()); 52 | $handler->init; 53 | 54 | my $hdl; $hdl = AnyEvent::Handle->new( 55 | fh => \*STDIN, 56 | on_error => sub { 57 | AE::log error => 'on_error: ' . $_[2]; 58 | $hdl->destroy; 59 | exit; 60 | }, 61 | on_eof => sub { 62 | AE::log info => 'Client disconnected'; 63 | $hdl->destroy; 64 | exit; 65 | } 66 | ); 67 | 68 | $hdl->on_read(sub { 69 | my ($h) = @_; 70 | 71 | $h->push_read(line => "\015\012\015\012", sub { 72 | AE::log debug => 'header: ' . $_[1]; 73 | }); 74 | 75 | $h->push_read(json => sub { 76 | my $json = JSON->new->utf8(1)->canonical(1)->encode($_[1]); 77 | AE::log debug => 'data: ' . $json; 78 | 79 | my $response = $handler->rpc->parse($json); 80 | return unless $response; 81 | 82 | my $length = length $response; 83 | AE::log info => 'resp: ' . $response; 84 | 85 | print "Content-Length: $length\r\n\r\n$response"; 86 | }); 87 | }); 88 | 89 | AE::cv->recv; 90 | } 91 | 92 | 93 | 1; 94 | __END__ 95 | 96 | =encoding utf-8 97 | 98 | =head1 NAME 99 | 100 | App::Monastery - Perl Language Server 101 | 102 | =head1 DESCRIPTION 103 | 104 | App::Monastery is a language server based on the L. 105 | Currently Monastery supports a subset of v3.0 of the protocol. 106 | 107 | More can be read about what a Language Server is for at L 108 | but the idea is to allow editors/IDEs to support a variety of languages 109 | without needing to reimplement lang-specific features themselves or, 110 | as in most cases, not implementing them at all. 111 | 112 | This server is very much B but it has some mildly 113 | intelligent completion options and should be easily expanded with 114 | L to handle formatting and C or L 115 | to provide more useful diagnostics to the user. 116 | 117 | =head1 INSTALLATION 118 | 119 | This can be installed from the repo directly or for convenience, there 120 | is a fatpacked version in author/. 121 | 122 | # install using cpanm 123 | cpanm -i git@github.com:ccakes/p5-app-monastery.git 124 | 125 | # or download fatpacked script 126 | curl -sL -o /usr/local/bin/monastery-fatpack https://raw.githubusercontent.com/ccakes/p5-app-monastery/master/author/monastery-fatpack 127 | 128 | =head1 TODO 129 | 130 | =over 4 131 | 132 | =item B 133 | 134 | This file is a mess, the C<< $rpc->register >> calls need to be abstracted, 135 | ideally into a controller-type model to make adding functionality 136 | simpler. 137 | 138 | =item B 139 | 140 | Need to work out when best to trigger this, probably on 141 | C. C would be nicer but a bit 142 | spammy. This should be C rather as it's syntax errors. 143 | 144 | =item B 145 | 146 | =item B 147 | 148 | =item B 149 | 150 | =back 151 | 152 | =head1 LICENSE 153 | 154 | Copyright (C) Cameron Daniel. 155 | 156 | This library is free software; you can redistribute it and/or modify 157 | it under the same terms as Perl itself. 158 | 159 | =head1 AUTHOR 160 | 161 | Cameron Daniel Ecam.daniel@gmail.comE 162 | 163 | =cut 164 | 165 | -------------------------------------------------------------------------------- /lib/App/Monastery/Handler.pm: -------------------------------------------------------------------------------- 1 | package App::Monastery::Handler; 2 | 3 | use feature ':5.10'; 4 | use strict; 5 | use warnings; 6 | 7 | our $VERSION = '0.02'; 8 | 9 | use URI; 10 | use JSON; 11 | use FindBin; 12 | use File::Spec; 13 | 14 | use DBM::Deep; 15 | use JSON::RPC::Spec; 16 | use File::Find::Rule; 17 | use Number::Bytes::Human qw(format_bytes); 18 | 19 | use Perl::Tidy; 20 | use App::Monastery::Document; 21 | 22 | use Class::Tiny qw(_db rpc logger workspace buffer); 23 | 24 | sub BUILD { 25 | my ($self, $args) = @_; 26 | 27 | my $dbm_file = $ENV{MONASTERY_DBM} || File::Spec->catfile( File::Spec->tmpdir, 'app-monastery.db' ); 28 | $self->logger->log(info => '[DBM] Using file: ' . $dbm_file); 29 | $self->logger->log(info => '[DBM] Using ' . format_bytes( (stat $dbm_file)[7] )) if -f $dbm_file; 30 | 31 | $self->_db({_file => $dbm_file }); 32 | $self->buffer({}); 33 | $self->rpc( JSON::RPC::Spec->new ); 34 | } 35 | 36 | sub load_from_directory { 37 | my $self = shift; 38 | my @dirs = @_ > 1 ? @_ : ($_[0]); 39 | 40 | my @files = File::Find::Rule->new->name('*.pm')->in(@dirs); 41 | 42 | ################################## 43 | # Parse files and return structure for importing 44 | my %data; 45 | foreach my $file (@files) { 46 | my $doc = App::Monastery::Document->new(file => $file); 47 | next unless $doc; 48 | 49 | my $record = $doc->record or next; 50 | $data{packages}{ $doc->package } = $record; 51 | } 52 | 53 | return \%data; 54 | } 55 | 56 | sub db { 57 | my ($self) = @_; 58 | 59 | return $self->_db->{$$} if $self->_db->{$$}; # return open handle for this process if one exists 60 | 61 | my $dbm = DBM::Deep->new( 62 | file => $self->_db->{_file}, 63 | locking => 1, 64 | autoflush => 1, 65 | num_txns => 3 66 | ); 67 | 68 | # Test DBM 69 | eval { 70 | $dbm->begin_work; 71 | $dbm->{key} = 'foo'; 72 | $dbm->rollback; 73 | }; 74 | if ($@) { AE::log error => $self->_db->{_file} . ' could not be written'; die } 75 | 76 | $self->_db->{$$} = $dbm; 77 | return $self->_db->{$$}; 78 | } 79 | 80 | sub load_buffer { 81 | my ($self, $uri, $src) = @_; 82 | 83 | my @lines = split /\n/, $src; 84 | 85 | $self->buffer->{$uri} = { 86 | lines => \@lines, 87 | doc => App::Monastery::Document->new(buf => $src) 88 | }; 89 | } 90 | 91 | sub drop_buffer { delete $_[0]->buffer->{$_[1]} } 92 | 93 | sub init { 94 | my ($self) = @_; 95 | 96 | $self->logger->log(info => __PACKAGE__ . '->init'); 97 | 98 | ################################## 99 | # Check that we haven't tryied to scan @INC 100 | # recently and if not, scan all modules 101 | # 102 | # TODO - un-hardcode timer 103 | my $reloaded = $self->db->{reloaded} // (time - 3600); 104 | if ($reloaded > (time - 3600)) { 105 | $self->logger->log(info => __PACKAGE__ . '->init: skipping run'); 106 | } 107 | else { 108 | $self->db->{reloaded} = time; 109 | 110 | unless (my $child = fork) { 111 | $self->logger->log(info => 'starting scan of @INC'); 112 | my $data = $self->load_from_directory(@INC); 113 | $self->logger->log(info => 'scan @INC complete'); 114 | 115 | # Merge into DBM as a single large commit for performance 116 | $self->db->begin_work; 117 | $self->db->import($data); 118 | $self->db->commit; 119 | 120 | exit; 121 | } 122 | } 123 | 124 | ################################## 125 | # Set up responders for RPC calls 126 | 127 | ################################## 128 | # Housekeeping 129 | $self->rpc->register('initialize' => sub { 130 | my ($params, $match) = @_; 131 | 132 | my $uri = URI->new($params->{rootUri}); 133 | die 'Cannot read directory ' . $uri->file unless -d $uri->file; 134 | $self->workspace( $uri->file ); 135 | 136 | # Parse files in current workspace 137 | unless (my $child = fork) { 138 | $self->logger->log(info => 'Loading symbols from ' . $uri->file); 139 | my $data = $self->load_from_directory($uri->file); 140 | 141 | $self->db->begin_work; 142 | $self->db->import($data); 143 | $self->db->commit; 144 | 145 | exit; 146 | } 147 | 148 | return { 149 | capabilities => { 150 | textDocumentSync => 1, # Send full document so we can rebuild PPI::Doc 151 | documentFormattingProvider => JSON::true, # Support "Format Code" via Perl::Tidy 152 | documentRangeFormattingProvider => JSON::true, # Supports range formatting 153 | documentSymbolProvider => JSON::false, # Support "Find all symbols" (TODO) 154 | workspaceSymbolProvider => JSON::false, # Support "Find all symbols in workspace" (TODO) 155 | definitionProvider => JSON::false, # Support "Go to definition" (TODO) 156 | referencesProvider => JSON::false, # Support "Find all references" (TODO) 157 | hoverProvider => JSON::false, # Support "Hover" 158 | completionProvider => { 159 | resolveProvider => JSON::false, 160 | triggerCharacters => [ '$', ':', '>' ] 161 | } 162 | } 163 | }; 164 | }); 165 | 166 | $self->rpc->register('shutdown' => sub { 167 | $self->workspace(undef); 168 | $self->buffer({}); 169 | }); 170 | 171 | $self->rpc->register('exit' => sub { exit }); 172 | 173 | ################################## 174 | # Document management 175 | $self->rpc->register('textDocument/didOpen' => sub { 176 | $self->load_buffer( $_[0]->{textDocument}{uri}, $_[0]->{textDocument}{text} ); 177 | }); 178 | 179 | $self->rpc->register('textDocument/didChange' => sub { 180 | $self->load_buffer( $_[0]->{textDocument}{uri}, $_[0]->{contentChanges}[0]{text} ); 181 | }); 182 | 183 | ################################## 184 | # Completion 185 | $self->rpc->register('textDocument/completion' => sub { 186 | my ($params, $match) = @_; 187 | 188 | my $response = { 189 | isIncomplete => JSON::true, 190 | items => [] 191 | }; 192 | 193 | my $buffer = $self->buffer->{ $params->{textDocument}{uri} }; 194 | 195 | my $line = $buffer->{lines}[ $params->{position}{line} ]; 196 | $self->logger->log(info => 'completion line: ' . $line); 197 | 198 | ################################## 199 | # Object/class methods 200 | if ($line =~ /([\$\:\w]+)->\w*$/) { 201 | ###### 202 | # Instance method 203 | my $match = $1; 204 | $self->logger->log(info => "completion: class method ($match)"); 205 | 206 | if ($match =~ /\$/) { 207 | $self->logger->log(info => 'class method: instance'); 208 | my $instances = $buffer->{doc}->object_instances; 209 | 210 | if ($instances->{$match}) { 211 | $response->{items} = $self->db->{packages}{ $instances->{$match} }{methods} if $self->db->{packages}{ $instances->{$match} }; 212 | $response->{isIncomplete} = JSON::false; 213 | 214 | return $response; 215 | } 216 | } 217 | else { 218 | ###### 219 | # Class method 220 | # 221 | $self->logger->log(info => 'class method: class'); 222 | if ($self->db->{packages}{$match}) { 223 | $self->logger->log(info => "class method: Found $match"); 224 | 225 | # my $dump = Data::Dumper->Dump([$self->db->{packages}{$match}], ['match']); 226 | # $self->logger->log(info => 'DUMP: ' . $dump); 227 | 228 | $response->{items} = $self->db->{packages}{$match}{methods}->export; 229 | $response->{isIncomplete} = JSON::false; 230 | 231 | return $response; 232 | } 233 | } 234 | } 235 | 236 | ################################## 237 | # Package names 238 | if ($line =~ /([\:\w]+)$/) { 239 | my $match = $1; 240 | $self->logger->log(info => "completion: package ($match)"); 241 | 242 | my @candidates = grep { $_ =~ /^$match/ } keys %{ $self->db->{packages} }; 243 | @candidates = map { { label => $_ } } @candidates; # formating 244 | $response->{items} = \@candidates; 245 | } 246 | 247 | return $response; # fallback 248 | }); 249 | 250 | ################################## 251 | # Perl::Tidy 252 | $self->rpc->register('textDocument/formatting' => sub { 253 | my ($output, $stderr, $log); 254 | my $buffer = $self->buffer->{ $_[0]->{textDocument}{uri} }; 255 | 256 | my $argv; 257 | $argv .= sprintf '-i %d ', delete $_[0]->{tabSize} if $_[0]{tabSize}; 258 | $argv .= '-t ' if ( exists $_[0]{insertSpaces} && !$_[0]{insertSpaces} ); 259 | delete $_[0]{insertSpaces}; 260 | 261 | foreach my $opt (keys %{ $_[0] }) { 262 | $argv .= sprintf '-%s %s', $opt, $_[0]{$opt}; 263 | } 264 | 265 | my $err = Perl::Tidy::tidy( 266 | source => $buffer->{lines}, 267 | destination => \$output, 268 | stderr => \$stderr, 269 | errorfile => \$stderr, 270 | logfile => \$log, 271 | argv => $argv 272 | ); 273 | 274 | return { 275 | start => { line => 0, character => 0 }, 276 | end => { 277 | line => scalar @{ $buffer->{lines} }, 278 | character => length $buffer->{lines}[-1] 279 | }, 280 | newText => $output 281 | }; 282 | }); 283 | } 284 | 285 | 1; 286 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2017 by Cameron Daniel . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2017 by Cameron Daniel . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2017 by Cameron Daniel . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | --------------------------------------------------------------------------------