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.
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: rizzy-lsp
3
+ Version: 0.1.0
4
+ Summary: Rizzylang Language Server Protocol implementation
5
+ Requires-Python: >=3.10
@@ -0,0 +1,318 @@
1
+ ![Status](https://img.shields.io/static/v1?label=status&message=unsolicited-diagnostics&color=purple)
2
+ ![Version](https://img.shields.io/static/v1?label=version&message=0.1.0&color=blue)
3
+ ![Protocol](https://img.shields.io/static/v1?label=protocol&message=LSP 3.17&color=blue)
4
+ ![Random](https://img.shields.io/static/v1?label=random-diagnostics&message=5%&color=orange)
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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: rizzy-lsp
3
+ Version: 0.1.0
4
+ Summary: Rizzylang Language Server Protocol implementation
5
+ Requires-Python: >=3.10
@@ -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,2 @@
1
+ [console_scripts]
2
+ rizzylang-lsp = rzlsp.server:main
@@ -0,0 +1 @@
1
+ rzlsp
@@ -0,0 +1 @@
1
+ from rzlsp.server import main
@@ -0,0 +1,2 @@
1
+ from rzlsp.server import main
2
+ 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()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+