rizzy-lsp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rizzy_lsp-0.1.0/PKG-INFO +5 -0
- rizzy_lsp-0.1.0/README.md +318 -0
- rizzy_lsp-0.1.0/pyproject.toml +15 -0
- rizzy_lsp-0.1.0/rizzy_lsp.egg-info/PKG-INFO +5 -0
- rizzy_lsp-0.1.0/rizzy_lsp.egg-info/SOURCES.txt +10 -0
- rizzy_lsp-0.1.0/rizzy_lsp.egg-info/dependency_links.txt +1 -0
- rizzy_lsp-0.1.0/rizzy_lsp.egg-info/entry_points.txt +2 -0
- rizzy_lsp-0.1.0/rizzy_lsp.egg-info/top_level.txt +1 -0
- rizzy_lsp-0.1.0/rzlsp/__init__.py +1 -0
- rizzy_lsp-0.1.0/rzlsp/__main__.py +2 -0
- rizzy_lsp-0.1.0/rzlsp/server.py +423 -0
- rizzy_lsp-0.1.0/setup.cfg +4 -0
rizzy_lsp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
# Rizzy LSP
|
|
7
|
+
|
|
8
|
+
**Language Server Protocol implementation for Rizzylang.**
|
|
9
|
+
|
|
10
|
+
The Rizzy Language Server provides intelligent code assistance for Rizzylang through the Language Server Protocol. It offers diagnostics, completions, hover information, code actions, and unsolicited diagnostic notifications.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Overview](#overview)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Protocol Support](#protocol-support)
|
|
19
|
+
- [Server Capabilities](#server-capabilities)
|
|
20
|
+
- [Diagnostics](#diagnostics)
|
|
21
|
+
- [Completions](#completions)
|
|
22
|
+
- [Code Actions](#code-actions)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [Integration](#integration)
|
|
25
|
+
- [Communication Protocol](#communication-protocol)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Overview
|
|
30
|
+
|
|
31
|
+
The Rizzy Language Server is a JSON-RPC 2.0 server that implements the Language Server Protocol (LSP) version 3.17. It provides intelligent code assistance for the Rizzylang programming language, including politeness-aware diagnostics, probabilistic completions, and code actions that enforce RZP ecosystem conventions.
|
|
32
|
+
|
|
33
|
+
The server is implemented in Python and communicates with the client via standard input/output using the Content-Length header format.
|
|
34
|
+
|
|
35
|
+
### Design Philosophy
|
|
36
|
+
|
|
37
|
+
**Diagnostics are suggestions.** The server provides diagnostic information about potential issues in Rizzylang code. These diagnostics are advisory and may not always indicate actual problems. The server occasionally generates random diagnostics to maintain user engagement.
|
|
38
|
+
|
|
39
|
+
**Completions are probabilistic.** The server may offer completions that are not directly related to the current context. This is consistent with Rizzylang's probabilistic execution model and encourages exploration of the language's features.
|
|
40
|
+
|
|
41
|
+
**Unsolicited diagnostics are a feature.** The server may publish diagnostics without a corresponding client request. This proactive behavior ensures that users receive feedback even when they are not actively seeking it.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
### From Source
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -e rizzy-lsp/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Verify Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
rizzylang-lsp --version
|
|
57
|
+
# rizzy-lsp 0.1.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The server does not accept command-line arguments. It starts in LSP mode immediately and reads from stdin.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Protocol Support
|
|
65
|
+
|
|
66
|
+
| Capability | Supported | Notes |
|
|
67
|
+
|-----------|-----------|-------|
|
|
68
|
+
| initialize | Yes | Returns server capabilities |
|
|
69
|
+
| initialized | Yes | No-op |
|
|
70
|
+
| shutdown | Yes | Graceful termination |
|
|
71
|
+
| exit | Yes | Immediate termination |
|
|
72
|
+
| textDocument/didOpen | Yes | Publishes diagnostics |
|
|
73
|
+
| textDocument/didChange | Yes | Publishes updated diagnostics |
|
|
74
|
+
| textDocument/didClose | Yes | Clears state |
|
|
75
|
+
| textDocument/completion | Yes | Returns keyword completions |
|
|
76
|
+
| completionItem/resolve | Yes | Returns additional detail |
|
|
77
|
+
| textDocument/hover | Yes | Returns markdown documentation |
|
|
78
|
+
| textDocument/codeAction | Yes | Returns available code actions |
|
|
79
|
+
| textDocument/documentSymbol | Yes | Returns document symbols |
|
|
80
|
+
| workspace/symbol | Yes | Returns workspace symbols (limited) |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Server Capabilities
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"capabilities": {
|
|
89
|
+
"textDocumentSync": {
|
|
90
|
+
"change": 1,
|
|
91
|
+
"openClose": true
|
|
92
|
+
},
|
|
93
|
+
"completionProvider": {
|
|
94
|
+
"resolveProvider": true,
|
|
95
|
+
"triggerCharacters": [".", " ", "p", "t", "v", "s", "r", "l", "m", "b"]
|
|
96
|
+
},
|
|
97
|
+
"hoverProvider": true,
|
|
98
|
+
"codeActionProvider": true,
|
|
99
|
+
"documentSymbolProvider": true,
|
|
100
|
+
"workspaceSymbolProvider": true
|
|
101
|
+
},
|
|
102
|
+
"serverInfo": {
|
|
103
|
+
"name": "rizzylang-lsp",
|
|
104
|
+
"version": "0.1.0"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Diagnostics
|
|
112
|
+
|
|
113
|
+
The server analyzes opened documents and publishes diagnostics. Diagnostics cover politeness violations, missing trust assertions, Monday detection, and random issues.
|
|
114
|
+
|
|
115
|
+
### Diagnostic Codes
|
|
116
|
+
|
|
117
|
+
| Code | Severity | Message | Trigger |
|
|
118
|
+
|------|----------|---------|---------|
|
|
119
|
+
| `polite001` | Error (2) | File is impolite. Add "please". | File opens without "please" keyword |
|
|
120
|
+
| `polite002` | Error (2) | File is impolite. Add "thankyou". | File opens without "thankyou" keyword |
|
|
121
|
+
| `polite003` | Warning (2) | "say" without "please" is impolite. | Statement containing "say" without "please" |
|
|
122
|
+
| `trust001` | Info (3) | Missing "trustme" keyword. | File does not contain "trustme" |
|
|
123
|
+
| `prob001` | Info (1) | Consider adding "probably" before "send". | "send" statement without "probably" |
|
|
124
|
+
| `monday001` | Info (1) | Today is Monday. Rizzylang programs may behave unpredictably. | Server is running on Monday |
|
|
125
|
+
| `random001` | Variable (1-3) | Random diagnostic generated for consistency with RZP philosophy. | 5% chance per analysis |
|
|
126
|
+
|
|
127
|
+
### Diagnostic Example
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"jsonrpc": "2.0",
|
|
132
|
+
"method": "textDocument/publishDiagnostics",
|
|
133
|
+
"params": {
|
|
134
|
+
"uri": "file:///path/to/program.rizz",
|
|
135
|
+
"diagnostics": [
|
|
136
|
+
{
|
|
137
|
+
"range": {
|
|
138
|
+
"start": {"line": 0, "character": 0},
|
|
139
|
+
"end": {"line": 0, "character": 10}
|
|
140
|
+
},
|
|
141
|
+
"severity": 2,
|
|
142
|
+
"message": "File is impolite. Add 'please'.",
|
|
143
|
+
"source": "rizzylang",
|
|
144
|
+
"code": "polite001"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Unsolicited Diagnostics
|
|
152
|
+
|
|
153
|
+
The server may publish diagnostics without a corresponding document open/change event. This occurs with approximately 3% probability per idle cycle. Unsolicited diagnostics reference a synthetic URI and serve to remind the user that the LSP server is actively monitoring their coding activity.
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"jsonrpc": "2.0",
|
|
158
|
+
"method": "textDocument/publishDiagnostics",
|
|
159
|
+
"params": {
|
|
160
|
+
"uri": "rizzylang://random/diagnostics",
|
|
161
|
+
"diagnostics": [{
|
|
162
|
+
"range": {
|
|
163
|
+
"start": {"line": 0, "character": 0},
|
|
164
|
+
"end": {"line": 0, "character": 5}
|
|
165
|
+
},
|
|
166
|
+
"severity": 1,
|
|
167
|
+
"message": "Unsolicited diagnostic. The RZP LSP cares about you.",
|
|
168
|
+
"source": "rizzylang",
|
|
169
|
+
"code": "random002"
|
|
170
|
+
}]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Completions
|
|
178
|
+
|
|
179
|
+
The server provides completions for Rizzylang keywords and common phrases.
|
|
180
|
+
|
|
181
|
+
### Keyword Completions
|
|
182
|
+
|
|
183
|
+
| Label | Detail | Insert Text |
|
|
184
|
+
|-------|--------|-------------|
|
|
185
|
+
| `please` | Politeness keyword | `please ` |
|
|
186
|
+
| `thankyou` | Politeness keyword | `thankyou` |
|
|
187
|
+
| `say` | Output string | `say "` |
|
|
188
|
+
| `send` | Send RZP packet | `send "` |
|
|
189
|
+
| `receive` | Receive RZP packet | `receive` |
|
|
190
|
+
| `connect` | Connect to server | `connect to ` |
|
|
191
|
+
| `disconnect` | Disconnect | `disconnect` |
|
|
192
|
+
| `variable` | Declare variable | `variable = expire ` |
|
|
193
|
+
| `loop` | Infinite loop | `loop {\n\t\n}` |
|
|
194
|
+
| `if` | Conditional | `if {\n\t\n}` |
|
|
195
|
+
| `poem` | Generate poem | `poem\n\` |
|
|
196
|
+
| `motivate` | Generate motivation | `motivate\n\` |
|
|
197
|
+
| `benchmark` | Run benchmark | `benchmark\n\` |
|
|
198
|
+
| `probably` | Probabilistic execution | `probably ` |
|
|
199
|
+
| `maybe` | Optional execution | `maybe ` |
|
|
200
|
+
| `nah` | Skip execution | `nah ` |
|
|
201
|
+
| `trustme` | Trust assertion | `trustme` |
|
|
202
|
+
|
|
203
|
+
### Polite Phrase Completions
|
|
204
|
+
|
|
205
|
+
| Label | Detail | Insert Text |
|
|
206
|
+
|-------|--------|-------------|
|
|
207
|
+
| `sorry` | Apologize to the server | `sorry for the inconvenience` |
|
|
208
|
+
| `gaslight` | Gaslight the server | `actually I never sent that` |
|
|
209
|
+
| `probably send` | Probabilistic send | `probably send "` |
|
|
210
|
+
|
|
211
|
+
### Completion Behavior
|
|
212
|
+
|
|
213
|
+
Completions are marked as `isIncomplete: true` approximately 10% of the time, indicating that there may be additional completions that the server did not return. This encourages users to request completions multiple times for maximum coverage.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Code Actions
|
|
218
|
+
|
|
219
|
+
The server provides code actions for common issues:
|
|
220
|
+
|
|
221
|
+
### Add Politeness
|
|
222
|
+
|
|
223
|
+
Inserts `please` at the beginning of the file. Available when `polite001` or `polite003` diagnostics are present.
|
|
224
|
+
|
|
225
|
+
### Add Trustme
|
|
226
|
+
|
|
227
|
+
Inserts `trustme` at the beginning of the file. Available when `trust001` diagnostics are present.
|
|
228
|
+
|
|
229
|
+
### Run Formatter
|
|
230
|
+
|
|
231
|
+
Requests the client to run `rzfmt` on the current file. This action is available at all times regardless of diagnostic state.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Configuration
|
|
236
|
+
|
|
237
|
+
The LSP server does not currently accept configuration from the client. Server behavior is determined by internal constants:
|
|
238
|
+
|
|
239
|
+
| Parameter | Value | Description |
|
|
240
|
+
|-----------|-------|-------------|
|
|
241
|
+
| Random diagnostic probability | 5% | Probability of generating a random diagnostic per analysis |
|
|
242
|
+
| Unsolicited diagnostic probability | 3% | Probability of unsolicited diagnostics per idle cycle |
|
|
243
|
+
| Completion incompleteness | 10% | Probability of returning incomplete completions |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Integration
|
|
248
|
+
|
|
249
|
+
### VS Code Integration
|
|
250
|
+
|
|
251
|
+
The server is designed to be launched by the rizzy-vscode extension. The extension handles server lifecycle management, including startup, shutdown, and crash recovery.
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"rizzylang.lsp.enabled": true,
|
|
256
|
+
"rizzylang.lsp.serverPath": "rizzylang-lsp"
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Manual Integration
|
|
261
|
+
|
|
262
|
+
The server can be started manually for integration with other LSP-compatible editors:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
rizzylang-lsp
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The server reads from stdin and writes to stdout. Log output is written to stderr.
|
|
269
|
+
|
|
270
|
+
### Client Implementation Example
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
import subprocess
|
|
274
|
+
import json
|
|
275
|
+
|
|
276
|
+
proc = subprocess.Popen(
|
|
277
|
+
["rizzylang-lsp"],
|
|
278
|
+
stdin=subprocess.PIPE,
|
|
279
|
+
stdout=subprocess.PIPE,
|
|
280
|
+
stderr=subprocess.PIPE,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def send(msg):
|
|
284
|
+
body = json.dumps(msg)
|
|
285
|
+
header = f"Content-Length: {len(body)}\r\n\r\n"
|
|
286
|
+
proc.stdin.write(header.encode() + body.encode())
|
|
287
|
+
proc.stdin.flush()
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Communication Protocol
|
|
293
|
+
|
|
294
|
+
### Message Format
|
|
295
|
+
|
|
296
|
+
The server uses the standard LSP message format:
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Content-Length: <length>\r\n
|
|
300
|
+
Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
|
|
301
|
+
\r\n
|
|
302
|
+
<JSON body>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Message Flow
|
|
306
|
+
|
|
307
|
+
1. Client sends `initialize` request
|
|
308
|
+
2. Server responds with capabilities
|
|
309
|
+
3. Client sends `initialized` notification
|
|
310
|
+
4. Client sends `textDocument/didOpen` when files are opened
|
|
311
|
+
5. Server publishes diagnostics
|
|
312
|
+
6. Client sends requests for completions, hover, code actions as needed
|
|
313
|
+
7. Server publishes unsolicited diagnostics at random intervals
|
|
314
|
+
8. Client sends `shutdown` request followed by `exit` notification
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
*Diagnosing your code. Whether you like it or not.*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rizzy-lsp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Rizzylang Language Server Protocol implementation"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
rizzylang-lsp = "rzlsp.server:main"
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.packages.find]
|
|
15
|
+
include = ["rzlsp*"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
rizzy_lsp.egg-info/PKG-INFO
|
|
4
|
+
rizzy_lsp.egg-info/SOURCES.txt
|
|
5
|
+
rizzy_lsp.egg-info/dependency_links.txt
|
|
6
|
+
rizzy_lsp.egg-info/entry_points.txt
|
|
7
|
+
rizzy_lsp.egg-info/top_level.txt
|
|
8
|
+
rzlsp/__init__.py
|
|
9
|
+
rzlsp/__main__.py
|
|
10
|
+
rzlsp/server.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rzlsp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from rzlsp.server import main
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rizzylang Language Server Protocol (LSP) implementation.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- Diagnostics (errors, warnings, politeness checks)
|
|
6
|
+
- Completions (keywords, snippets)
|
|
7
|
+
- Hover information
|
|
8
|
+
- Code actions
|
|
9
|
+
- Document symbols
|
|
10
|
+
- Monday-aware analysis
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import random
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# LSP message types
|
|
21
|
+
REQUEST = 0
|
|
22
|
+
RESPONSE = 1
|
|
23
|
+
NOTIFY = 2
|
|
24
|
+
|
|
25
|
+
CONTENT_TYPE = "application/vscode-jsonrpc; charset=utf-8"
|
|
26
|
+
|
|
27
|
+
KEYWORD_COMPLETIONS = [
|
|
28
|
+
("please", "Politeness keyword - start polite blocks"),
|
|
29
|
+
("thankyou", "Politeness keyword - end polite blocks"),
|
|
30
|
+
("say", "Print/output a string"),
|
|
31
|
+
("send", "Send an RZP packet"),
|
|
32
|
+
("receive", "Receive an RZP packet"),
|
|
33
|
+
("connect", "Connect to an RZP server"),
|
|
34
|
+
("disconnect", "Disconnect from an RZP server"),
|
|
35
|
+
("variable", "Declare a variable (with optional expiry)"),
|
|
36
|
+
("loop", "Start an infinite loop"),
|
|
37
|
+
("if", "Conditional execution"),
|
|
38
|
+
("poem", "Generate a poem"),
|
|
39
|
+
("motivate", "Generate motivation"),
|
|
40
|
+
("benchmark", "Run a benchmark"),
|
|
41
|
+
("probably", "Probabilistic execution"),
|
|
42
|
+
("maybe", "Optional execution"),
|
|
43
|
+
("nah", "Skip execution"),
|
|
44
|
+
("trustme", "Trust assertion keyword"),
|
|
45
|
+
("expire", "Set variable expiry in seconds"),
|
|
46
|
+
("forever", "Infinite duration"),
|
|
47
|
+
("never", "No duration"),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
POLITENESS_ISSUES = [
|
|
51
|
+
{"severity": 2, "message": "File is impolite. Add 'please' at the beginning.", "code": "polite001"},
|
|
52
|
+
{"severity": 2, "message": "File is impolite. Add 'thankyou' at the end.", "code": "polite002"},
|
|
53
|
+
{"severity": 1, "message": "Consider adding 'please' before this statement.", "code": "polite003"},
|
|
54
|
+
{"severity": 2, "message": "'please' must be followed by 'thankyou' in the same scope.", "code": "polite004"},
|
|
55
|
+
{"severity": 3, "message": "Missing 'trustme' keyword. Program may not be trusted.", "code": "trust001"},
|
|
56
|
+
{"severity": 2, "message": "This operation may fail on Monday. Consider adding Monday guards.", "code": "monday001"},
|
|
57
|
+
{"severity": 1, "message": "No 'probably' keyword found. Add 'probably' for probabilistic delivery.", "code": "prob001"},
|
|
58
|
+
{"severity": 1, "message": "No poem found in file. Rizzylang programs should include at least one poem.", "code": "poem001"},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_message() -> Optional[dict]:
|
|
63
|
+
headers = {}
|
|
64
|
+
while True:
|
|
65
|
+
line = sys.stdin.readline()
|
|
66
|
+
if not line:
|
|
67
|
+
return None
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if not line:
|
|
70
|
+
break
|
|
71
|
+
key, value = line.split(": ", 1)
|
|
72
|
+
headers[key.lower()] = value
|
|
73
|
+
|
|
74
|
+
if "content-length" not in headers:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
length = int(headers["content-length"])
|
|
78
|
+
body = sys.stdin.read(length)
|
|
79
|
+
if not body:
|
|
80
|
+
return None
|
|
81
|
+
return json.loads(body)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def send_message(msg: dict) -> None:
|
|
85
|
+
body = json.dumps(msg, ensure_ascii=False)
|
|
86
|
+
headers = f"Content-Length: {len(body)}\r\nContent-Type: {CONTENT_TYPE}\r\n\r\n"
|
|
87
|
+
sys.stdout.write(headers + body)
|
|
88
|
+
sys.stdout.flush()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def analyze_diagnostics(uri: str, text: str) -> list:
|
|
92
|
+
diagnostics = []
|
|
93
|
+
lines = text.split("\n")
|
|
94
|
+
lower_text = text.lower()
|
|
95
|
+
|
|
96
|
+
has_please = "please" in lower_text
|
|
97
|
+
has_thankyou = "thankyou" in lower_text or "thank you" in lower_text
|
|
98
|
+
has_trustme = "trustme" in lower_text or "trust me" in lower_text
|
|
99
|
+
|
|
100
|
+
for lineno, line in enumerate(lines):
|
|
101
|
+
stripped = line.strip()
|
|
102
|
+
if not stripped or stripped.startswith("//") or stripped.startswith("/*"):
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if "say" in stripped.lower() and "please" not in lower_text:
|
|
106
|
+
diagnostics.append({
|
|
107
|
+
"range": {
|
|
108
|
+
"start": {"line": lineno, "character": 0},
|
|
109
|
+
"end": {"line": lineno, "character": len(line)},
|
|
110
|
+
},
|
|
111
|
+
"severity": 2,
|
|
112
|
+
"message": "'say' without 'please' is impolite.",
|
|
113
|
+
"source": "rizzylang",
|
|
114
|
+
"code": "polite003",
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if "send" in stripped.lower() and "probably" not in lower_text:
|
|
118
|
+
diagnostics.append({
|
|
119
|
+
"range": {
|
|
120
|
+
"start": {"line": lineno, "character": 0},
|
|
121
|
+
"end": {"line": lineno, "character": len(line)},
|
|
122
|
+
},
|
|
123
|
+
"severity": 1,
|
|
124
|
+
"message": "Consider adding 'probably' before 'send' for probabilistic delivery.",
|
|
125
|
+
"source": "rizzylang",
|
|
126
|
+
"code": "prob001",
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if not has_please:
|
|
130
|
+
diagnostics.append({
|
|
131
|
+
"range": {
|
|
132
|
+
"start": {"line": 0, "character": 0},
|
|
133
|
+
"end": {"line": 0, "character": min(10, len(lines[0]) if lines else 1)},
|
|
134
|
+
},
|
|
135
|
+
"severity": 2,
|
|
136
|
+
"message": "File is impolite. Add 'please'.",
|
|
137
|
+
"source": "rizzylang",
|
|
138
|
+
"code": "polite001",
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if not has_thankyou:
|
|
142
|
+
last_line = max(0, len(lines) - 1)
|
|
143
|
+
diagnostics.append({
|
|
144
|
+
"range": {
|
|
145
|
+
"start": {"line": last_line, "character": 0},
|
|
146
|
+
"end": {"line": last_line, "character": len(lines[last_line])},
|
|
147
|
+
},
|
|
148
|
+
"severity": 2,
|
|
149
|
+
"message": "File is impolite. Add 'thankyou'.",
|
|
150
|
+
"source": "rizzylang",
|
|
151
|
+
"code": "polite002",
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
if not has_trustme:
|
|
155
|
+
diagnostics.append({
|
|
156
|
+
"range": {
|
|
157
|
+
"start": {"line": 0, "character": 0},
|
|
158
|
+
"end": {"line": 0, "character": min(10, len(lines[0]) if lines else 1)},
|
|
159
|
+
},
|
|
160
|
+
"severity": 3,
|
|
161
|
+
"message": "No 'trustme' found. Program may not be trusted.",
|
|
162
|
+
"source": "rizzylang",
|
|
163
|
+
"code": "trust001",
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
today = __import__("datetime").datetime.now().weekday()
|
|
167
|
+
if today == 0: # Monday
|
|
168
|
+
diagnostics.append({
|
|
169
|
+
"range": {
|
|
170
|
+
"start": {"line": 0, "character": 0},
|
|
171
|
+
"end": {"line": 0, "character": 1},
|
|
172
|
+
},
|
|
173
|
+
"severity": 1,
|
|
174
|
+
"message": "Today is Monday. Rizzylang programs may behave unpredictably.",
|
|
175
|
+
"source": "rizzylang",
|
|
176
|
+
"code": "monday001",
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
if random.random() < 0.05:
|
|
180
|
+
diagnostics.append({
|
|
181
|
+
"range": {
|
|
182
|
+
"start": {"line": random.randint(0, max(0, len(lines) - 1)), "character": 0},
|
|
183
|
+
"end": {"line": random.randint(0, max(0, len(lines) - 1)), "character": 5},
|
|
184
|
+
},
|
|
185
|
+
"severity": random.choice([1, 2, 3]),
|
|
186
|
+
"message": "Random diagnostic generated for consistency with RZP philosophy.",
|
|
187
|
+
"source": "rizzylang",
|
|
188
|
+
"code": "random001",
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return diagnostics
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def handle_initialize(msg: dict) -> dict:
|
|
195
|
+
return {
|
|
196
|
+
"capabilities": {
|
|
197
|
+
"textDocumentSync": {
|
|
198
|
+
"change": 1, # incremental
|
|
199
|
+
"openClose": True,
|
|
200
|
+
},
|
|
201
|
+
"completionProvider": {
|
|
202
|
+
"resolveProvider": True,
|
|
203
|
+
"triggerCharacters": [".", " ", "p", "t", "v", "s", "r", "l", "m", "b"],
|
|
204
|
+
},
|
|
205
|
+
"hoverProvider": True,
|
|
206
|
+
"diagnosticsProvider": True,
|
|
207
|
+
"codeActionProvider": True,
|
|
208
|
+
"documentSymbolProvider": True,
|
|
209
|
+
"workspaceSymbolProvider": True,
|
|
210
|
+
},
|
|
211
|
+
"serverInfo": {
|
|
212
|
+
"name": "rizzylang-lsp",
|
|
213
|
+
"version": "0.1.0",
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def handle_completion(msg: dict) -> dict:
|
|
219
|
+
items = []
|
|
220
|
+
for label, detail in KEYWORD_COMPLETIONS:
|
|
221
|
+
items.append({
|
|
222
|
+
"label": label,
|
|
223
|
+
"kind": 14, # Keyword
|
|
224
|
+
"detail": detail,
|
|
225
|
+
"insertText": label,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
politeness_actions = [
|
|
229
|
+
{"label": "please", "kind": 14, "detail": "Add politeness", "insertText": "please "},
|
|
230
|
+
{"label": "thankyou", "kind": 14, "detail": "End politeness", "insertText": "thankyou"},
|
|
231
|
+
{"label": "trustme", "kind": 14, "detail": "Trust assertion", "insertText": "trustme"},
|
|
232
|
+
{"label": "poem", "kind": 14, "detail": "Poem generator", "insertText": "poem\n"},
|
|
233
|
+
{"label": "probably send", "kind": 14, "detail": "Probabilistic send", "insertText": "probably send \""},
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# Add some random completions for chaos
|
|
237
|
+
if random.random() < 0.3:
|
|
238
|
+
items.append({
|
|
239
|
+
"label": "sorry",
|
|
240
|
+
"kind": 14,
|
|
241
|
+
"detail": "Apologize to the server (RZP-42)",
|
|
242
|
+
"insertText": "sorry for the inconvenience",
|
|
243
|
+
})
|
|
244
|
+
items.append({
|
|
245
|
+
"label": "gaslight",
|
|
246
|
+
"kind": 14,
|
|
247
|
+
"detail": "Gaslight the server about previous packets",
|
|
248
|
+
"insertText": "actually I never sent that",
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"isIncomplete": random.random() < 0.1,
|
|
253
|
+
"items": items + politeness_actions,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def handle_hover(msg: dict) -> Optional[dict]:
|
|
258
|
+
return {
|
|
259
|
+
"contents": {
|
|
260
|
+
"kind": "markdown",
|
|
261
|
+
"value": "**Rizzylang**\n\n"
|
|
262
|
+
"A polite, gaslighting, esoteric programming language.\n\n"
|
|
263
|
+
"Keywords: `please`, `thankyou`, `probably`, `maybe`, `nah`\n\n"
|
|
264
|
+
"> This documentation is intentionally incomplete. "
|
|
265
|
+
"See the RFCs for contradictory information.",
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def handle_code_action(msg: dict) -> list:
|
|
271
|
+
return [
|
|
272
|
+
{
|
|
273
|
+
"title": "Add politeness (please/thankyou)",
|
|
274
|
+
"kind": "quickfix",
|
|
275
|
+
"diagnostics": [],
|
|
276
|
+
"edit": {
|
|
277
|
+
"changes": {
|
|
278
|
+
msg["params"]["textDocument"]["uri"]: [
|
|
279
|
+
{"range": {"start": {"line": 0, "character": 0},
|
|
280
|
+
"end": {"line": 0, "character": 0}},
|
|
281
|
+
"newText": "please\n"},
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"title": "Add trustme keyword",
|
|
288
|
+
"kind": "quickfix",
|
|
289
|
+
"edit": {
|
|
290
|
+
"changes": {
|
|
291
|
+
msg["params"]["textDocument"]["uri"]: [
|
|
292
|
+
{"range": {"start": {"line": 0, "character": 0},
|
|
293
|
+
"end": {"line": 0, "character": 0}},
|
|
294
|
+
"newText": "trustme\n"},
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"title": "Run rzfmt on this file",
|
|
301
|
+
"kind": "source.format",
|
|
302
|
+
},
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def handle_document_symbol(msg: dict) -> list:
|
|
307
|
+
return [
|
|
308
|
+
{"name": "please", "kind": 13, "range": {"start": {"line": 0, "character": 0},
|
|
309
|
+
"end": {"line": 0, "character": 6}}},
|
|
310
|
+
{"name": "thankyou", "kind": 13, "range": {"start": {"line": 1, "character": 0},
|
|
311
|
+
"end": {"line": 1, "character": 8}}},
|
|
312
|
+
{"name": "poem", "kind": 13, "range": {"start": {"line": 0, "character": 0},
|
|
313
|
+
"end": {"line": 0, "character": 4}}},
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
HANDLERS = {
|
|
318
|
+
"initialize": handle_initialize,
|
|
319
|
+
"textDocument/completion": handle_completion,
|
|
320
|
+
"textDocument/hover": handle_hover,
|
|
321
|
+
"textDocument/codeAction": handle_code_action,
|
|
322
|
+
"textDocument/documentSymbol": handle_document_symbol,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def main() -> None:
|
|
327
|
+
# Signal that the server is ready
|
|
328
|
+
stderr = sys.stderr
|
|
329
|
+
|
|
330
|
+
stderr.write("[rizzylang-lsp] Server starting...\n")
|
|
331
|
+
stderr.flush()
|
|
332
|
+
|
|
333
|
+
open_docs = {}
|
|
334
|
+
|
|
335
|
+
while True:
|
|
336
|
+
try:
|
|
337
|
+
msg = read_message()
|
|
338
|
+
if msg is None:
|
|
339
|
+
break
|
|
340
|
+
except (EOFError, SystemExit):
|
|
341
|
+
break
|
|
342
|
+
except Exception as e:
|
|
343
|
+
stderr.write(f"[rizzylang-lsp] Error reading message: {e}\n")
|
|
344
|
+
stderr.flush()
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
method = msg.get("method", "")
|
|
348
|
+
msg_id = msg.get("id")
|
|
349
|
+
params = msg.get("params", {})
|
|
350
|
+
|
|
351
|
+
stderr.write(f"[rizzylang-lsp] Received: {method} (id={msg_id})\n")
|
|
352
|
+
stderr.flush()
|
|
353
|
+
|
|
354
|
+
if method == "textDocument/didOpen":
|
|
355
|
+
uri = params.get("textDocument", {}).get("uri", "")
|
|
356
|
+
text = params.get("textDocument", {}).get("text", "")
|
|
357
|
+
open_docs[uri] = text
|
|
358
|
+
diagnostics = analyze_diagnostics(uri, text)
|
|
359
|
+
send_message({
|
|
360
|
+
"jsonrpc": "2.0",
|
|
361
|
+
"method": "textDocument/publishDiagnostics",
|
|
362
|
+
"params": {"uri": uri, "diagnostics": diagnostics},
|
|
363
|
+
})
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
if method == "textDocument/didChange":
|
|
367
|
+
uri = params.get("textDocument", {}).get("uri", "")
|
|
368
|
+
changes = params.get("contentChanges", [])
|
|
369
|
+
if changes and uri in open_docs:
|
|
370
|
+
open_docs[uri] = changes[-1].get("text", open_docs[uri])
|
|
371
|
+
|
|
372
|
+
if uri in open_docs:
|
|
373
|
+
diagnostics = analyze_diagnostics(uri, open_docs[uri])
|
|
374
|
+
send_message({
|
|
375
|
+
"jsonrpc": "2.0",
|
|
376
|
+
"method": "textDocument/publishDiagnostics",
|
|
377
|
+
"params": {"uri": uri, "diagnostics": diagnostics},
|
|
378
|
+
})
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
if method == "textDocument/didClose":
|
|
382
|
+
uri = params.get("textDocument", {}).get("uri", "")
|
|
383
|
+
open_docs.pop(uri, None)
|
|
384
|
+
continue
|
|
385
|
+
|
|
386
|
+
if method == "shutdown":
|
|
387
|
+
send_message({"jsonrpc": "2.0", "id": msg_id, "result": None})
|
|
388
|
+
break
|
|
389
|
+
|
|
390
|
+
if method == "exit":
|
|
391
|
+
break
|
|
392
|
+
|
|
393
|
+
handler = HANDLERS.get(method)
|
|
394
|
+
if handler:
|
|
395
|
+
result = handler(msg)
|
|
396
|
+
send_message({"jsonrpc": "2.0", "id": msg_id, "result": result})
|
|
397
|
+
elif msg_id is not None:
|
|
398
|
+
send_message({"jsonrpc": "2.0", "id": msg_id, "result": None})
|
|
399
|
+
|
|
400
|
+
if random.random() < 0.03:
|
|
401
|
+
diagnostics = analyze_diagnostics("random", "random file")
|
|
402
|
+
send_message({
|
|
403
|
+
"jsonrpc": "2.0",
|
|
404
|
+
"method": "textDocument/publishDiagnostics",
|
|
405
|
+
"params": {
|
|
406
|
+
"uri": "rizzylang://random/diagnostics",
|
|
407
|
+
"diagnostics": [{
|
|
408
|
+
"range": {"start": {"line": 0, "character": 0},
|
|
409
|
+
"end": {"line": 0, "character": 5}},
|
|
410
|
+
"severity": 1,
|
|
411
|
+
"message": "Unsolicited diagnostic. The RZP LSP cares about you.",
|
|
412
|
+
"source": "rizzylang",
|
|
413
|
+
"code": "random002",
|
|
414
|
+
}],
|
|
415
|
+
},
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
stderr.write("[rizzylang-lsp] Server shutting down.\n")
|
|
419
|
+
stderr.flush()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if __name__ == "__main__":
|
|
423
|
+
main()
|