File Coverage

blib/lib/PLS/Server.pm
Criterion Covered Total %
statement 121 126 96.0
branch 33 46 71.7
condition 4 9 44.4
subroutine 27 29 93.1
pod 0 10 0.0
total 185 220 84.0


line stmt bran cond sub pod time code
1             package PLS::Server;
2              
3 9     9   780675 use strict;
  9         74  
  9         209  
4 9     9   36 use warnings;
  9         18  
  9         179  
5              
6 9     9   4575 use Future;
  9         105741  
  9         232  
7 9     9   3040 use Future::Queue;
  9         7061  
  9         226  
8 9     9   3507 use Future::Utils;
  9         17188  
  9         345  
9 9     9   5309 use IO::Async::Loop;
  9         254858  
  9         294  
10 9     9   3384 use IO::Async::Signal;
  9         38140  
  9         247  
11 9     9   4483 use IO::Async::Stream;
  9         175048  
  9         336  
12 9     9   64 use IO::Handle;
  9         18  
  9         338  
13 9     9   36 use Scalar::Util qw(blessed);
  9         18  
  9         334  
14              
15 9     9   2981 use PLS::JSON;
  9         18  
  9         363  
16 9     9   3423 use PLS::Server::Request::Factory;
  9         128  
  9         1101  
17 9     9   63 use PLS::Server::Response;
  9         24  
  9         196  
18 9     9   3099 use PLS::Server::Response::Cancelled;
  9         18  
  9         10042  
19              
20             =head1 NAME
21              
22             PLS::Server
23              
24             =head1 DESCRIPTION
25              
26             Perl Language Server
27              
28             This server communicates to a language client through STDIN/STDOUT.
29              
30             =head1 SYNOPSIS
31              
32             my $server = PLS::Server->new();
33             my $exit_code = $server->run();
34              
35             exit $exit_code;
36              
37             =cut
38              
39             sub new
40             {
41 7     7 0 73106520 my ($class) = @_;
42              
43             return
44 7         920 bless {
45             loop => IO::Async::Loop->new(),
46             stream => undef,
47             running_futures => {},
48             pending_requests => {}
49             }, $class;
50             } ## end sub new
51              
52             sub run
53             {
54 7     7 0 1169 my ($self) = @_;
55              
56             $self->{stream} = IO::Async::Stream->new_for_stdio(
57             autoflush => 0,
58             on_read => sub {
59 7     7   289183 my $size = 0;
60              
61             return sub {
62 24         90410 my ($stream, $buffref, $eof) = @_;
63              
64 24 50       230 exit if $eof;
65              
66 24 50       163 unless ($size)
67             {
68 24 50       686 return 0 unless ($$buffref =~ s/^(.*?)\r\n\r\n//s);
69 24         258 my $headers = $1;
70              
71 24         193 my %headers = map { split /: / } grep { length } split /\r\n/, $headers;
  24         222  
  24         205  
72 24         156 $size = $headers{'Content-Length'};
73 24 100       297 die 'no Content-Length header provided' unless $size;
74             } ## end unless ($size)
75              
76 23 50       224 return 0 if (length($$buffref) < $size);
77              
78 23         163 my $json = substr $$buffref, 0, $size, '';
79 23         112 $size = 0;
80              
81 23         3571 my $content = decode_json $json;
82              
83 23         203 $self->handle_client_message($content);
84 23         1310 return 1;
85 7         273 };
86             }
87 7         880 );
88              
89 7         6380 $self->{loop}->add($self->{stream});
90             $self->{loop}->add(
91             IO::Async::Signal->new(name => 'TERM',
92 4     4   14685 on_receipt => sub { $self->stop(0) })
93 7 50       5619 )
94             if ($^O ne 'MSWin32');
95              
96 7         6014 my $exit_code = $self->{loop}->run();
97              
98 6 50       1793 return (length $exit_code) ? $exit_code : 1;
99             } ## end sub run
100              
101             sub handle_client_message
102             {
103 23     23 0 102 my ($self, $message) = @_;
104              
105 23 100       130 if (length $message->{method})
106             {
107 19         387 $message = PLS::Server::Request::Factory->new($message);
108              
109 19 100 66     560 if (blessed($message) and $message->isa('PLS::Server::Response'))
110             {
111 2         30 $self->send_message($message);
112 2         25 return;
113             }
114             } ## end if (length $message->{...})
115             else
116             {
117 4         104 $message = PLS::Server::Response->new($message);
118             }
119              
120 21 50       137 return unless blessed($message);
121              
122 21 100       235 if ($message->isa('PLS::Server::Request'))
123             {
124 17         97 $self->handle_client_request($message);
125             }
126 21 100       332 if ($message->isa('PLS::Server::Response'))
127             {
128 4         51 $self->handle_client_response($message);
129             }
130              
131 21         138 return;
132             } ## end sub handle_client_message
133              
134             sub send_server_request
135             {
136 30     30 0 719 my ($self, $request) = @_;
137              
138 30 50       187 return unless blessed($request);
139              
140 30 100       489 if ($request->isa('PLS::Server::Request'))
    50          
141             {
142 28         143 $self->handle_server_request($request);
143             }
144             elsif ($request->isa('Future'))
145             {
146             $request->on_done(
147             sub {
148 1     1   107 my ($request) = @_;
149              
150 1         13 $self->handle_server_request($request);
151             }
152 2         78 )->retain();
153             } ## end elsif ($request->isa('Future'...))
154 30         193 return;
155             } ## end sub send_server_request
156              
157             sub send_message
158             {
159 38     38 0 140 my ($self, $message) = @_;
160              
161 38 50 33     498 return if (not blessed($message) or not $message->isa('PLS::Server::Message'));
162 38         332 my $json = $message->serialize();
163 38         104 my $length = length $$json;
164 38         339 $self->{stream}->write("Content-Length: $length\r\n\r\n$$json")->retain();
165              
166 38         10347 return;
167             } ## end sub send_message
168              
169             sub handle_client_request
170             {
171 17     17 0 65 my ($self, $request) = @_;
172              
173 17         336 my $response = $request->service($self);
174              
175 17 100       1584 if (blessed($response))
176             {
177 7 100       221 if ($response->isa('PLS::Server::Response'))
    50          
178             {
179 6         100 $self->send_message($response);
180             }
181             elsif ($response->isa('Future'))
182             {
183 1 50       15 $self->{running_futures}{$request->{id}} = $response if (length $request->{id});
184              
185             $response->on_done(
186             sub {
187 0     0   0 my ($response) = @_;
188 0         0 $self->send_message($response);
189             }
190             )->on_cancel(
191             sub {
192 1     1   65 $self->send_message(PLS::Server::Response::Cancelled->new(id => $request->{id}));
193             }
194 1         25 );
195             } ## end elsif ($response->isa('Future'...))
196             } ## end if (blessed($response)...)
197              
198 17         259 return;
199             } ## end sub handle_client_request
200              
201             sub handle_client_response
202             {
203 4     4 0 38 my ($self, $response) = @_;
204              
205 4         34 my $request = $self->{pending_requests}{$response->{id}};
206              
207 4 50 33     160 if (blessed($request) and $request->isa('PLS::Server::Request'))
208             {
209 4         190 $request->handle_response($response, $self);
210             }
211              
212 4         67 return;
213             } ## end sub handle_client_response
214              
215             sub handle_server_request
216             {
217 29     29 0 101 my ($self, $request) = @_;
218              
219 29 100       131 if ($request->{notification})
220             {
221 14         66 delete $request->{notification};
222             }
223             else
224             {
225 15         37 $request->{id} = ++$self->{last_request_id};
226 15         85 $self->{pending_requests}{$request->{id}} = $request;
227             }
228              
229 29 100       110 delete $self->{running_futures}{$request->{id}} if (length $request->{id});
230 29         99 $self->send_message($request);
231 29         74 return;
232             } ## end sub handle_server_request
233              
234             sub handle_server_response
235             {
236 0     0 0 0 my ($self, $response) = @_;
237              
238 0         0 $self->send_message($response);
239 0         0 return;
240             } ## end sub handle_server_response
241              
242             sub stop
243             {
244 6     6 0 44 my ($self, $exit_code) = @_;
245              
246 6         198 $self->{loop}->stop($exit_code);
247              
248 6         116 return;
249             } ## end sub stop
250              
251             1;