Skip to content

Commit 5511ee5

Browse files
authored
Add argument parser feature to the LSP. (#218)
Add the argument parser feature to the LSP and modified the Main class. The argument parser will be able to parse the help and port-number arguments for now. For port-number argument, both flag argument and positional argument are supported. Port number is optional, and defaults to `0` to use standard in/out. The positional port argument is deprecated, but still supported.
1 parent 3b681c8 commit 5511ee5

File tree

3 files changed

+246
-79
lines changed

3 files changed

+246
-79
lines changed

src/main/java/software/amazon/smithy/lsp/Main.java

+34-79
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@
1515

1616
package software.amazon.smithy.lsp;
1717

18-
import java.io.IOException;
1918
import java.io.InputStream;
2019
import java.io.OutputStream;
2120
import java.net.Socket;
22-
import java.util.Optional;
23-
import org.eclipse.lsp4j.jsonrpc.Launcher;
2421
import org.eclipse.lsp4j.launch.LSPLauncher;
25-
import org.eclipse.lsp4j.services.LanguageClient;
22+
import software.amazon.smithy.cli.AnsiColorFormatter;
23+
import software.amazon.smithy.cli.CliPrinter;
24+
import software.amazon.smithy.cli.HelpPrinter;
2625

2726
/**
2827
* Main launcher for the Language server, started by the editor.
@@ -32,90 +31,46 @@ private Main() {
3231
}
3332

3433
/**
35-
* Launch the LSP and wait for it to terminate.
36-
*
37-
* @param in input stream for communication
38-
* @param out output stream for communication
39-
* @return Empty Optional if service terminated successfully, error otherwise
34+
* Main entry point for the language server.
35+
* @param args Arguments passed to the server.
36+
* @throws Exception If there is an error starting the server.
4037
*/
41-
public static Optional<Exception> launch(InputStream in, OutputStream out) {
42-
SmithyLanguageServer server = new SmithyLanguageServer();
43-
Launcher<LanguageClient> launcher = LSPLauncher.createServerLauncher(
44-
server,
45-
exitOnClose(in),
46-
out);
47-
48-
LanguageClient client = launcher.getRemoteProxy();
49-
50-
server.connect(client);
51-
try {
52-
launcher.startListening().get();
53-
return Optional.empty();
54-
} catch (Exception e) {
55-
return Optional.of(e);
38+
public static void main(String[] args) throws Exception {
39+
var serverArguments = ServerArguments.create(args);
40+
if (serverArguments.help()) {
41+
printHelp(serverArguments);
42+
System.exit(0);
5643
}
44+
45+
launch(serverArguments);
5746
}
5847

59-
private static InputStream exitOnClose(InputStream delegate) {
60-
return new InputStream() {
61-
@Override
62-
public int read() throws IOException {
63-
int result = delegate.read();
64-
if (result < 0) {
65-
System.exit(0);
66-
}
67-
return result;
48+
private static void launch(ServerArguments serverArguments) throws Exception {
49+
if (serverArguments.useSocket()) {
50+
try (var socket = new Socket("localhost", serverArguments.port())) {
51+
startServer(socket.getInputStream(), socket.getOutputStream());
6852
}
69-
};
53+
} else {
54+
startServer(System.in, System.out);
55+
}
7056
}
7157

72-
/**
73-
* @param args Arguments passed to launch server. First argument must either be
74-
* a port number for socket connection, or 0 to use STDIN and STDOUT
75-
* for communication
76-
*/
77-
public static void main(String[] args) {
78-
79-
Socket socket = null;
80-
InputStream in;
81-
OutputStream out;
58+
private static void startServer(InputStream in, OutputStream out) throws Exception {
59+
var server = new SmithyLanguageServer();
60+
var launcher = LSPLauncher.createServerLauncher(server, in, out);
8261

83-
try {
84-
String port = args[0];
85-
// If port is set to "0", use System.in/System.out.
86-
if (port.equals("0")) {
87-
in = System.in;
88-
out = System.out;
89-
} else {
90-
socket = new Socket("localhost", Integer.parseInt(port));
91-
in = socket.getInputStream();
92-
out = socket.getOutputStream();
93-
}
94-
95-
Optional<Exception> launchFailure = launch(in, out);
62+
var client = launcher.getRemoteProxy();
63+
server.connect(client);
9664

97-
if (launchFailure.isPresent()) {
98-
throw launchFailure.get();
99-
} else {
100-
System.out.println("Server terminated without errors");
101-
}
102-
} catch (ArrayIndexOutOfBoundsException e) {
103-
System.out.println("Missing port argument");
104-
} catch (NumberFormatException e) {
105-
System.out.println("Port number must be a valid integer");
106-
} catch (Exception e) {
107-
System.out.println(e);
65+
launcher.startListening().get();
66+
}
10867

109-
e.printStackTrace();
110-
} finally {
111-
try {
112-
if (socket != null) {
113-
socket.close();
114-
}
115-
} catch (Exception e) {
116-
System.out.println("Failed to close the socket");
117-
System.out.println(e);
118-
}
119-
}
68+
private static void printHelp(ServerArguments serverArguments) {
69+
CliPrinter printer = CliPrinter.fromOutputStream(System.out);
70+
HelpPrinter helpPrinter = new HelpPrinter("smithy-language-server");
71+
serverArguments.registerHelp(helpPrinter);
72+
helpPrinter.summary("Run the Smithy Language Server.");
73+
helpPrinter.print(AnsiColorFormatter.AUTO, printer);
74+
printer.flush();
12075
}
12176
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.lsp;
7+
8+
import java.util.function.Consumer;
9+
import software.amazon.smithy.cli.ArgumentReceiver;
10+
import software.amazon.smithy.cli.Arguments;
11+
import software.amazon.smithy.cli.CliError;
12+
import software.amazon.smithy.cli.HelpPrinter;
13+
14+
/**
15+
* Options and Params available for LSP.
16+
*/
17+
final class ServerArguments implements ArgumentReceiver {
18+
19+
private static final int MIN_PORT = 0;
20+
private static final int MAX_PORT = 65535;
21+
private static final int DEFAULT_PORT = 0; // Default value for unset port number.
22+
private static final String HELP = "--help";
23+
private static final String HELP_SHORT = "-h";
24+
private static final String PORT = "--port";
25+
private static final String PORT_SHORT = "-p";
26+
private static final String PORT_POSITIONAL = "<port>";
27+
private int port = DEFAULT_PORT;
28+
private boolean help = false;
29+
30+
31+
static ServerArguments create(String[] args) {
32+
Arguments arguments = Arguments.of(args);
33+
var serverArguments = new ServerArguments();
34+
arguments.addReceiver(serverArguments);
35+
var positional = arguments.getPositional();
36+
if (!positional.isEmpty()) {
37+
serverArguments.port = serverArguments.validatePortNumber(positional.getFirst());
38+
}
39+
return serverArguments;
40+
}
41+
42+
@Override
43+
public void registerHelp(HelpPrinter printer) {
44+
printer.option(HELP, HELP_SHORT, "Print this help output.");
45+
printer.param(PORT, PORT_SHORT, "PORT",
46+
"The port to use for talking to the client. When not specified, or set to 0, "
47+
+ "standard in/out is used. Standard in/out is preferred, "
48+
+ "so usually this shouldn't be specified.");
49+
printer.option(PORT_POSITIONAL, null, "Deprecated: use --port instead. When not specified, or set to 0, "
50+
+ "standard in/out is used. Standard in/out is preferred, so usually this shouldn't be specified.");
51+
}
52+
53+
@Override
54+
public boolean testOption(String name) {
55+
if (name.equals(HELP) || name.equals(HELP_SHORT)) {
56+
help = true;
57+
return true;
58+
}
59+
return false;
60+
}
61+
62+
@Override
63+
public Consumer<String> testParameter(String name) {
64+
if (name.equals(PORT_SHORT) || name.equals(PORT)) {
65+
return value -> {
66+
port = validatePortNumber(value);
67+
};
68+
}
69+
return null;
70+
}
71+
72+
int port() {
73+
return port;
74+
}
75+
76+
boolean help() {
77+
return help;
78+
}
79+
80+
boolean useSocket() {
81+
return port != 0;
82+
}
83+
84+
private int validatePortNumber(String portStr) {
85+
try {
86+
int portNumber = Integer.parseInt(portStr);
87+
if (portNumber < MIN_PORT || portNumber > MAX_PORT) {
88+
throw invalidPort(portStr);
89+
} else {
90+
return portNumber;
91+
}
92+
} catch (NumberFormatException e) {
93+
throw invalidPort(portStr);
94+
}
95+
}
96+
97+
private static CliError invalidPort(String portStr) {
98+
return new CliError("Invalid port number: expected an integer between "
99+
+ MIN_PORT + " and " + MAX_PORT + ", inclusive. Was: " + portStr);
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package software.amazon.smithy.lsp;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.PrintStream;
5+
import org.junit.jupiter.api.Test;
6+
import software.amazon.smithy.cli.CliError;
7+
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
import static org.junit.jupiter.api.Assertions.assertThrows;
12+
import static org.junit.jupiter.api.Assertions.assertTrue;
13+
14+
public class ServerArgumentsTest {
15+
@Test
16+
void validPositionalPortNumber() {
17+
String[] args = {"1"};
18+
ServerArguments serverArguments = ServerArguments.create(args);
19+
assertEquals(1, serverArguments.port());
20+
assertFalse(serverArguments.help());
21+
assertTrue(serverArguments.useSocket());
22+
}
23+
24+
@Test
25+
void invalidPositionalPortNumber() {
26+
String[] args = {"65536"};
27+
assertThrows(CliError.class,()-> {ServerArguments.create(args);});
28+
29+
}
30+
31+
@Test
32+
void invalidFlagPortNumber() {
33+
String[] args = {"-p","65536"};
34+
assertThrows(CliError.class,()-> {ServerArguments.create(args);});
35+
}
36+
37+
@Test
38+
void validFlagPortNumberShort() {
39+
String[] args = {"-p","100"};
40+
ServerArguments serverArguments = ServerArguments.create(args);
41+
assertEquals(100, serverArguments.port());
42+
assertFalse(serverArguments.help());
43+
assertTrue(serverArguments.useSocket());
44+
}
45+
46+
@Test
47+
void defaultPortNumber() {
48+
String[] args = {};
49+
ServerArguments serverArguments = ServerArguments.create(args);
50+
51+
assertEquals(0, serverArguments.port());
52+
assertFalse(serverArguments.help());
53+
assertFalse(serverArguments.useSocket());
54+
}
55+
56+
@Test
57+
void defaultPortNumberInArg() {
58+
String[] args = {"0"};
59+
ServerArguments serverArguments = ServerArguments.create(args);
60+
assertEquals(0, serverArguments.port());
61+
assertFalse(serverArguments.help());
62+
assertFalse(serverArguments.useSocket());
63+
}
64+
65+
@Test
66+
void defaultPortNumberWithFlag() {
67+
String[] args = {"--port","0"};
68+
ServerArguments serverArguments = ServerArguments.create(args);
69+
assertEquals(0, serverArguments.port());
70+
assertFalse(serverArguments.help());
71+
assertFalse(serverArguments.useSocket());
72+
}
73+
74+
@Test
75+
void defaultPortNumberWithShotFlag() {
76+
String[] args = {"-p","0"};
77+
ServerArguments serverArguments = ServerArguments.create(args);
78+
assertEquals(0, serverArguments.port());
79+
assertFalse(serverArguments.help());
80+
assertFalse(serverArguments.useSocket());
81+
}
82+
83+
@Test
84+
void validFlagPortNumber() {
85+
String[] args = {"--port","200"};
86+
ServerArguments serverArguments = ServerArguments.create(args);
87+
assertEquals(200, serverArguments.port());
88+
}
89+
90+
@Test
91+
void invalidFlag() {
92+
String[] args = {"--foo"};
93+
assertThrows(CliError.class,()-> {ServerArguments.create(args);});
94+
}
95+
96+
@Test
97+
void validHelpShort() {
98+
String[] args = {"-h"};
99+
ServerArguments serverArguments = ServerArguments.create(args);
100+
assertTrue(serverArguments.help());
101+
assertFalse(serverArguments.useSocket());
102+
}
103+
104+
@Test
105+
void validHelp() {
106+
String[] args = {"--help"};
107+
ServerArguments serverArguments = ServerArguments.create(args);
108+
assertTrue(serverArguments.help());
109+
assertFalse(serverArguments.useSocket());
110+
}
111+
}

0 commit comments

Comments
 (0)