Skip to content

Commit 1afbd27

Browse files
feat: implement multi-transport support and CLI refactoring (#127)
closes #126 ### Summary This PR implements a flexible transport layer for `protols`, significantly improving debugging capabilities and performance. ### Key Changes - **Multi-transport Support**: Fully implemented TCP (port/socket) and Pipe (Unix Domain Sockets / Named Pipes) transports in accordance with the [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#implementationConsiderations). - **CLI Refactoring**: moved from `main.rs` to a separate file. - **Improved Performance**: Native async I/O support for socket/pipe transports. - Safety check for Unix sockets to prevent accidental overwriting of regular files. - **Debugging & DX**: - Added a dedicated TCP mode for easier debugging in Dev Containers. - Included VS Code `launch.json` and task configurations for a seamless debug workflow. - Added a `docs/debugging.md` guide. - **Testing**: Added unit tests for CLI parsing and cross-platform path normalization. - **Project Metadata**: Updated `Cargo.toml` description for better clarity. As i see from <https://github.com/coder3101/tree-sitter-proto>, the LSP supports `proto2`, `proto3`, and `editions`, rather than just `proto3`. The updated CLI `--help` output: <img width="1441" height="329" alt="image" src="https://github.com/user-attachments/assets/2dfe0a94-3d0f-48d0-8d71-00e21b41a658" /> ### Notes ⚠️ I was unable to check the build on Windows. I'd suggest merging it after #128 to be confident about Windows-specific code. Support for [`--clientProcessId`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#implementationConsiderations) is intentionally omitted from this PR: > To support the case that the editor starting a server crashes an editor should also pass its process id to the server. This allows the server to monitor the editor process and to shutdown itself if the editor process dies. The process id passed on the command line should be the same as the one passed in the initialize parameters. The command line argument to use is `--clientProcessId`. I'd proposed making it in a separate follow-up to keep the focus on the transport layer infrastructure. --------- Signed-off-by: Ashar <ashar786khan@gmail.com> Co-authored-by: Ashar <coder3101@users.noreply.github.com> Co-authored-by: Ashar <ashar786khan@gmail.com>
1 parent be80bd3 commit 1afbd27

10 files changed

Lines changed: 706 additions & 96 deletions

File tree

.devcontainer/dotfiles/init.lua

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,30 @@ vim.api.nvim_create_autocmd("FileType", {
44
pattern = "proto",
55
callback = function()
66
local bin = vim.fn.getcwd() .. "/target/debug/protols"
7-
if vim.fn.executable(bin) == 1 then
8-
vim.lsp.start({
9-
name = "protols-dev",
10-
cmd = { bin },
11-
root_dir = vim.fn.getcwd(),
12-
init_options = { include_paths = { vim.fn.getcwd() } },
13-
on_init = function(client)
14-
client.notify("$/setTrace", { value = "verbose" })
15-
end,
16-
})
7+
local debug_port = os.getenv("LSP_DEBUG_PORT")
8+
9+
local lsp_config = {
10+
name = "protols-dev",
11+
root_dir = vim.fn.getcwd(),
12+
init_options = { include_paths = { vim.fn.getcwd() } },
13+
on_init = function(client)
14+
client.notify("$/setTrace", { value = "verbose" })
15+
end,
16+
}
17+
18+
if debug_port and debug_port ~= "" then
19+
lsp_config.cmd = vim.lsp.rpc.connect("127.0.0.1", tonumber(debug_port))
20+
vim.notify("protols-dev: connecting to port " .. debug_port, vim.log.levels.INFO)
1721
else
18-
vim.notify("protols-dev: binary not found. Run 'cargo build' first!", 3)
22+
if vim.fn.executable(bin) == 1 then
23+
lsp_config.cmd = { bin }
24+
else
25+
vim.notify("protols-dev: binary not found. Run 'cargo build' first!", vim.log.levels.ERROR)
26+
return
27+
end
1928
end
29+
30+
vim.lsp.start(lsp_config)
2031
end,
2132
})
2233

.vscode/launch.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"type": "lldb",
6+
"request": "launch",
7+
"name": "Debug protols via TCP",
8+
"program": "${workspaceFolder}/target/debug/protols",
9+
"args": [
10+
"--port",
11+
"${config:protols.debugPort}"
12+
],
13+
"cwd": "${workspaceFolder}",
14+
"sourceLanguages": [
15+
"rust"
16+
],
17+
"terminal": "console",
18+
"presentation": {
19+
"hidden": false,
20+
"group": "lsp-debug",
21+
"order": 1
22+
},
23+
"preLaunchTask": "Neovim (TCP Debug Mode)",
24+
"expressions": "native"
25+
}
26+
]
27+
}

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"protols.debugPort": "7301"
3+
}

.vscode/tasks.json

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"icon": {
6+
"id": "package"
7+
},
8+
"type": "cargo",
9+
"command": "build",
10+
"problemMatcher": [
11+
"$rustc"
12+
],
13+
"group": {
14+
"kind": "build",
15+
"isDefault": true
16+
},
17+
"label": "Rust: cargo build",
18+
"presentation": {
19+
"echo": true,
20+
"reveal": "always",
21+
"focus": false,
22+
"panel": "shared",
23+
"showReuseMessage": true,
24+
"clear": true
25+
}
26+
},
27+
{
28+
"icon": {
29+
"id": "check-all"
30+
},
31+
"type": "cargo",
32+
"command": "clippy",
33+
"problemMatcher": [
34+
"$rustc"
35+
],
36+
"group": "none",
37+
"label": "Rust: cargo clippy",
38+
"presentation": {
39+
"echo": true,
40+
"reveal": "always",
41+
"focus": false,
42+
"panel": "shared",
43+
"showReuseMessage": true,
44+
"clear": true
45+
}
46+
},
47+
{
48+
"icon": {
49+
"id": "beaker"
50+
},
51+
"type": "cargo",
52+
"command": "test",
53+
"problemMatcher": [
54+
"$rustc"
55+
],
56+
"group": {
57+
"kind": "test",
58+
"isDefault": true
59+
},
60+
"label": "Rust: cargo test",
61+
"presentation": {
62+
"echo": true,
63+
"reveal": "always",
64+
"focus": false,
65+
"panel": "shared",
66+
"showReuseMessage": true,
67+
"clear": true
68+
}
69+
},
70+
{
71+
"label": "Neovim (TCP Debug Mode)",
72+
"icon": {
73+
"id": "debug-console"
74+
},
75+
"type": "process",
76+
"command": "nvim",
77+
"isBackground": true,
78+
"problemMatcher": {
79+
"pattern": {
80+
"regexp": "."
81+
},
82+
"background": {
83+
"activeOnStart": true,
84+
"beginsPattern": ".",
85+
"endsPattern": "."
86+
}
87+
},
88+
"options": {
89+
"env": {
90+
"LSP_DEBUG_PORT": "${config:protols.debugPort}"
91+
}
92+
},
93+
"group": "none",
94+
"presentation": {
95+
"reveal": "always",
96+
"panel": "dedicated",
97+
"group": "lsp-debug",
98+
"focus": true,
99+
"close": false,
100+
"clear": true,
101+
}
102+
}
103+
]
104+
}

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "protols"
3-
description = "Language server for proto3 files"
3+
description = "Language server for Protocol Buffers files"
44
version = "0.13.4"
55
edition = "2024"
66
license = "MIT"

README.md

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- [For Neovim](#for-neovim)
2626
- [Setting Include Paths in Neovim](#setting-include-paths-in-neovim)
2727
- [Command Line Options](#command-line-options)
28+
- [Examples](#examples)
2829
- [For Visual Studio Code](#for-visual-studio-code)
2930
- [Configuration](#%EF%B8%8Fconfiguration)
3031
- [Sample `protols.toml`](#sample-protolstoml)
@@ -45,6 +46,9 @@
4546
- [Packaging](#-packaging)
4647
- [Contributing](#-contributing)
4748
- [Setting Up Locally](#setting-up-locally)
49+
- [Option 1: Using Dev Containers (Easiest)](#option-1-using-dev-containers-easiest)
50+
- [Option 2: Manual Setup](#option-2-manual-setup)
51+
- [Debugging](#-debugging)
4852
- [License](#-license)
4953

5054
---
@@ -87,19 +91,62 @@ require'lspconfig'.protols.setup{
8791

8892
Protols supports various command line options to customize its behavior:
8993

90-
```
91-
protols [OPTIONS]
94+
```text
95+
Usage: protols [OPTIONS]
9296
9397
Options:
94-
-i, --include-paths <INCLUDE_PATHS> Include paths for proto files, comma-separated
95-
-V, --version Print version information
96-
-h, --help Print help information
98+
-i, --include-paths <INCLUDE_PATHS> Include paths for proto files, comma-separated (can be used multiple times)
99+
-h, --help Print help
100+
-V, --version Print version
101+
102+
Transport:
103+
--stdio Use stdin/stdout for communication (default)
104+
--socket <ADDR> Use TCP communication with a specific address and port. Examples: "192.168.1.10:5005" or "0.0.0.0:5005"
105+
--port <PORT> Use TCP communication on localhost with a specific port. Example: "5005"
106+
--pipe <PATH> Use Unix domain socket (Linux/macOS) or Named Pipe (Windows). Examples: "/tmp/protols.sock" or "protols-pipe" (Windows)
97107
```
98108

99-
For example, to specify include paths when starting the language server:
109+
#### Examples
110+
111+
##### Specify include paths
112+
113+
You can provide include paths using a comma-separated list or by repeating the
114+
flag:
100115

101116
```bash
102117
protols --include-paths=/path/to/protos,/another/path/to/protos
118+
# or
119+
protols -i /path/to/protos -i /another/path/to/protos
120+
```
121+
122+
##### Communication via TCP
123+
TCP transport is useful when the language server and the IDE run in different
124+
environments.
125+
126+
- **Localhost only**: Connect within the same machine.
127+
```bash
128+
protols --port 7301
129+
```
130+
- **Container/Docker mode**: Listen on all interfaces to allow access from the
131+
host machine to the container.
132+
```bash
133+
protols --socket 0.0.0.0:7301
134+
```
135+
- **Specific interface**: Listen on a specific network IP.
136+
```bash
137+
protols --socket 192.168.1.10:7301
138+
```
139+
140+
##### Communication via Unix Domain Socket
141+
On Linux or macOS, you can use a socket file for communication:
142+
```bash
143+
protols --pipe /tmp/protols.sock
144+
```
145+
146+
##### Communication via Named Pipes (Windows)
147+
On Windows, you can specify a pipe name. `protols` handles the `\\.\pipe\` prefix automatically:
148+
```bash
149+
protols --pipe protols-pipe
103150
```
104151

105152
### For Visual Studio Code
@@ -265,6 +312,12 @@ testing.
265312
cargo test
266313
```
267314

315+
### 🐞 Debugging
316+
317+
If you want to contribute or debug the server logic, please refer to the
318+
[Debugging Guide](docs/debugging.md) for instructions on setting up a TCP-based
319+
debug session with VS Code and Neovim.
320+
268321
---
269322

270323
## 📄 License

docs/debugging.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Debugging protols
2+
3+
Since `protols` is a language server, debugging it directly via standard I/O can
4+
be challenging. Furthermore, attaching a debugger to a running process inside a
5+
Dev Container is often restricted by security policies.
6+
7+
The recommended way to debug is to start the server in TCP mode and connect to
8+
it with your LSP client.
9+
10+
## Debugging with VS Code and Neovim
11+
12+
The project includes a pre-configured `launch.json` for VS Code that automates
13+
the debugging setup.
14+
15+
### 1. Start the Debug Session
16+
17+
In VS Code, go to the **Run and Debug** view and select **"Debug protols via
18+
TCP"**.
19+
20+
This will:
21+
- Build the project in debug mode.
22+
- Start the server listening on a TCP port (default: `7301`).
23+
- Automatically open a new terminal with **Neovim** inside the Dev Container.
24+
25+
### 2. Connect Neovim
26+
27+
The Neovim instance in the Dev Container is pre-configured to detect the
28+
`LSP_DEBUG_PORT` environment variable.
29+
30+
1. Wait for the message in the VS Code Debug Console: `LSP server listening on TCP: 127.0.0.1:7301`.
31+
2. In the opened Neovim terminal, open any `.proto` file (e.g., `:e sample/simple.proto`).
32+
3. Neovim will automatically connect to the debugged server instance via the specified port.
33+
34+
### 3. Verify Connection
35+
To ensure the debugger is working and the server is responding:
36+
1. Set a breakpoint in the Rust code (e.g., in `src/parser/docsymbol.rs`).
37+
2. In Neovim, trigger an LSP request, such as fetching document symbols:
38+
```vim
39+
:lua vim.lsp.buf.document_symbol()
40+
```
41+
3. The debugger should hit your breakpoint in VS Code.
42+
43+
## Manual Debugging
44+
45+
If you prefer to run the components manually:
46+
47+
1. **Start the server:**
48+
```bash
49+
cargo run -- --port 7301
50+
```
51+
2. **Start Neovim:**
52+
```bash
53+
LSP_DEBUG_PORT=7301 nvim your_file.proto
54+
```

0 commit comments

Comments
 (0)