File Coverage

blib/lib/PLS/Server.pm
Criterion Covered Total %
statement 125 131 95.4
branch 33 46 71.7
condition 4 9 44.4
subroutine 27 29 93.1
pod 0 10 0.0
total 189 225 84.0


line stmt bran cond sub pod time code
1             package PLS::Server;
2              
3 11     11   2803323 use strict;
  11         30  
  11         410  
4 11     11   53 use warnings;
  11         22  
  11         541  
5              
6 11     11   8133 use Future;
  11         200501  
  11         469  
7 11     11   6404 use Future::Queue;
  11         17301  
  11         829  
8 11     11   7111 use Future::Utils;
  11         37303  
  11         678  
9 11     11   10579 use IO::Async::Loop;
  11         567561  
  11         570  
10 11     11   8292 use IO::Async::Signal;
  11         77026  
  11         478  
11 11     11   7935 use IO::Async::Stream;
  11         484215  
  11         1086  
12 11     11   126 use IO::Handle;
  11         31  
  11         696  
13 11     11   212 use Scalar::Util qw(blessed);
  11         38  
  11         678  
14              
15 11     11   7895 use PLS::JSON;
  11         43  
  11         858  
16 11     11   6539 use PLS::Server::Request::Factory;
  11         731  
  11         2754  
17 11     11   97 use PLS::Server::Response;
  11         22  
  11         574  
18 11     11   7239 use PLS::Server::Response::Cancelled;
  11         33  
  11         20192  
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 149820011 my ($class) = @_;
42              
43             return
44 7         1291 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 1775 my ($self) = @_;
55              
56             $self->{stream} = IO::Async::Stream->new_for_stdio(
57             autoflush => 0,
58             on_read => sub {
59 7     7   472309 my $size = 0;
60              
61             return sub {
62 24         202076 my ($stream, $buffref, $eof) = @_;
63              
64 24 50       451 exit if $eof;
65              
66 24 50       183 if (not $size)
67             {
68 24 50       141 if (${$buffref} =~ s/^(.*?)\r\n\r\n//s)
  24         1338  
69             {
70 24         203 my $headers = $1;
71              
72 24         200 my %headers = map { split /: / } grep { length } split /\r\n/, $headers;
  24         495  
  24         96  
73 24         511 $size = $headers{'Content-Length'};
74 24 100       508 die 'no Content-Length header provided' unless $size;
75             } ## end if (${$buffref} =~ s/^(.*?)\r\n\r\n//s...)
76             else
77             {
78 0         0 return 0;
79             }
80             } ## end if (not $size)
81              
82 23 50       128 return 0 if (length(${$buffref}) < $size);
  23         311  
83              
84 23         95 my $json = substr ${$buffref}, 0, $size, '';
  23         262  
85 23         99 $size = 0;
86              
87 23         4901 my $content = decode_json $json;
88              
89 23         502 $self->handle_client_message($content);
90 23         1824 return 1;
91 7         484 };
92             }
93 7         1532 );
94              
95 7         10216 $self->{loop}->add($self->{stream});
96             $self->{loop}->add(
97             IO::Async::Signal->new(name => 'TERM',
98 4     4   55955 on_receipt => sub { $self->stop(0) })
99 7 50       17930 )
100             if ($^O ne 'MSWin32');
101              
102 7         30589 my $exit_code = $self->{loop}->run();
103              
104 6 50       3220 return (length $exit_code) ? $exit_code : 1;
105             } ## end sub run
106              
107             sub handle_client_message
108             {
109 23     23 0 123 my ($self, $message) = @_;
110              
111 23 100       539 if (length $message->{method})
112             {
113 19         699 $message = PLS::Server::Request::Factory->new($message);
114              
115 19 100 66     990 if (blessed($message) and $message->isa('PLS::Server::Response'))
116             {
117 2         98 $self->send_message($message);
118 2         45 return;
119             }
120             } ## end if (length $message->{...})
121             else
122             {
123 4         139 $message = PLS::Server::Response->new($message);
124             }
125              
126 21 50       180 return unless blessed($message);
127              
128 21 100       321 if ($message->isa('PLS::Server::Request'))
129             {
130 17         302 $self->handle_client_request($message);
131             }
132 21 100       544 if ($message->isa('PLS::Server::Response'))
133             {
134 4         145 $self->handle_client_response($message);
135             }
136              
137 21         152 return;
138             } ## end sub handle_client_message
139              
140             sub send_server_request
141             {
142 30     30 0 1187 my ($self, $request) = @_;
143              
144 30 50       151 return unless blessed($request);
145              
146 30 100       810 if ($request->isa('PLS::Server::Request'))
    50          
147             {
148 28         4355 $self->handle_server_request($request);
149             }
150             elsif ($request->isa('Future'))
151             {
152             $request->on_done(
153             sub {
154 1     1   150 my ($request) = @_;
155              
156 1         9 $self->handle_server_request($request);
157             }
158 2         165 )->retain();
159             } ## end elsif ($request->isa('Future'...))
160 30         293 return;
161             } ## end sub send_server_request
162              
163             sub send_message
164             {
165 38     38 0 102 my ($self, $message) = @_;
166              
167 38 50 33     639 return if (not blessed($message) or not $message->isa('PLS::Server::Message'));
168 38         464 my $json = $message->serialize();
169 38         75 my $length = length ${$json};
  38         109  
170 38         828 $self->{stream}->write("Content-Length: $length\r\n\r\n$$json")->retain();
171              
172 38         20393 return;
173             } ## end sub send_message
174              
175             sub handle_client_request
176             {
177 17     17 0 106 my ($self, $request) = @_;
178              
179 17         647 my $response = $request->service($self);
180              
181 17 100       6895 if (blessed($response))
182             {
183 7 100       204 if ($response->isa('PLS::Server::Response'))
    50          
184             {
185 6         109 $self->send_message($response);
186             }
187             elsif ($response->isa('Future'))
188             {
189 1 50       33 $self->{running_futures}{$request->{id}} = $response if (length $request->{id});
190              
191             $response->on_done(
192             sub {
193 0     0   0 my ($response) = @_;
194 0         0 $self->send_message($response);
195             }
196             )->on_cancel(
197             sub {
198 1     1   87 $self->send_message(PLS::Server::Response::Cancelled->new(id => $request->{id}));
199             }
200 1         38 );
201             } ## end elsif ($response->isa('Future'...))
202             } ## end if (blessed($response)...)
203              
204 17         481 return;
205             } ## end sub handle_client_request
206              
207             sub handle_client_response
208             {
209 4     4 0 48 my ($self, $response) = @_;
210              
211 4         72 my $request = $self->{pending_requests}{$response->{id}};
212              
213 4 50 33     176 if (blessed($request) and $request->isa('PLS::Server::Request'))
214             {
215 4         247 $request->handle_response($response, $self);
216             }
217              
218 4         19 return;
219             } ## end sub handle_client_response
220              
221             sub handle_server_request
222             {
223 29     29 0 81 my ($self, $request) = @_;
224              
225 29 100       255 if ($request->{notification})
226             {
227 14         39 delete $request->{notification};
228             }
229             else
230             {
231 15         161 $request->{id} = ++$self->{last_request_id};
232 15         236 $self->{pending_requests}{$request->{id}} = $request;
233             }
234              
235 29 100       225 delete $self->{running_futures}{$request->{id}} if (length $request->{id});
236 29         138 $self->send_message($request);
237 29         84 return;
238             } ## end sub handle_server_request
239              
240             sub handle_server_response
241             {
242 0     0 0 0 my ($self, $response) = @_;
243              
244 0         0 $self->send_message($response);
245 0         0 return;
246             } ## end sub handle_server_response
247              
248             sub stop
249             {
250 6     6 0 50 my ($self, $exit_code) = @_;
251              
252 6         261 $self->{loop}->stop($exit_code);
253              
254 6         153 return;
255             } ## end sub stop
256              
257             1;