Skip to content

Commit 53f6249

Browse files
committed
knohow on typescript lsp experiments
1 parent a0a1543 commit 53f6249

File tree

4 files changed

+189
-0
lines changed

4 files changed

+189
-0
lines changed

src/experiments/readme.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# TypeScript Language Server Client
2+
3+
A Node.js CLI tool for getting TypeScript/JavaScript code completions using the Language Server Protocol (LSP), inspired by Zed editor's implementation.
4+
5+
## Quick Start
6+
7+
```bash
8+
# Install dependencies
9+
npm install vscode-languageserver-protocol vscode-uri
10+
11+
# Run the tool
12+
node lsp.js /path/to/project /path/to/file.js 10:15
13+
```
14+
15+
Where:
16+
- `/path/to/project` is the root directory of your project
17+
- `/path/to/file.js` is the specific file you want completions for
18+
- `10:15` is the line and character position (zero-based) where you want completions
19+
20+
## Key Features
21+
22+
- Works with JavaScript, TypeScript, JSX, and TSX files
23+
- Automatically detects project configuration (tsconfig.json/jsconfig.json)
24+
- Uses locally installed TypeScript if available
25+
- Falls back to typescript-language-server's bundled TypeScript if needed
26+
- Provides proper property completions for imported modules
27+
- Robust message handling for LSP communication
28+
- Timeouts to prevent hanging on incomplete responses
29+
30+
## Common Challenges and Solutions
31+
32+
### 1. LSP Message Format
33+
34+
**Challenge**: The Language Server Protocol uses a specific message format with headers and JSON body.
35+
36+
**Solution**: Implemented proper binary buffer handling with Content-Length parsing:
37+
```javascript
38+
// Format for sending
39+
const jsonMessage = JSON.stringify(message);
40+
const contentLength = Buffer.byteLength(jsonMessage, 'utf8');
41+
const header = `Content-Length: ${contentLength}\r\n\r\n`;
42+
serverProcess.stdin.write(header + jsonMessage);
43+
44+
// Format for receiving
45+
// Handle message header first, then read the exact number of bytes for content
46+
```
47+
48+
### 2. Requests vs. Notifications
49+
50+
**Challenge**: LSP has two types of messages (requests that need responses and notifications that don't).
51+
52+
**Solution**: Implemented separate methods for each type:
53+
```javascript
54+
// For requests (require response)
55+
sendRequest: function(method, params) {
56+
// Includes an ID and expects a response
57+
}
58+
59+
// For notifications (no response)
60+
sendNotification: function(method, params) {
61+
// No ID, no response expected
62+
}
63+
```
64+
65+
### 3. Large Message Handling
66+
67+
**Challenge**: The server sometimes sends very large messages that can cause the client to hang.
68+
69+
**Solution**: Implemented binary buffer handling with progress tracking and timeouts:
70+
```javascript
71+
// Binary buffer concatenation
72+
buffer = Buffer.concat([buffer, chunk]);
73+
74+
// Progress tracking
75+
const percentComplete = ((buffer.length / contentLength) * 100).toFixed(1);
76+
77+
// Timeout mechanism for completion requests
78+
const completionPromise = getCompletions(server, documentUri, line, character);
79+
const timeoutPromise = new Promise((_, reject) =>
80+
setTimeout(() => reject(new Error('Completion request timed out')), 10000)
81+
);
82+
```
83+
84+
### 4. Import Resolution
85+
86+
**Challenge**: Code completions for imported modules weren't working initially.
87+
88+
**Solution**: While we implemented a `syncRelatedFiles` function to provide context to the language server, we discovered that this wasn't actually the main issue. The key fix was properly specifying the trigger character (see below).
89+
90+
**Note**: The file syncing code is currently prepared but may not be necessary in most cases as long as the trigger character is correctly specified:
91+
92+
```javascript
93+
// Find and open all related files in the directory
94+
async function syncRelatedFiles(server, projectRoot, targetFile) {
95+
// Opens JS/TS files in the same directory
96+
}
97+
```
98+
99+
### 5. Completion Parameters and Trigger Characters
100+
101+
**Challenge**: Property completions for object members weren't showing up at all. For example, with code like `config.se<cursor>rver.port`, no completions were being returned, even though VS Code would show property suggestions.
102+
103+
**Solution**: Adding the correct trigger character (".") to completion requests was critical:
104+
105+
```javascript
106+
// Before (no completions returned)
107+
{
108+
context: {
109+
triggerKind: 1, // Manual invocation
110+
// No trigger character specified
111+
}
112+
}
113+
114+
// After (property completions now work)
115+
{
116+
context: {
117+
triggerKind: 1, // Manual invocation
118+
triggerCharacter: "." // For property completions
119+
}
120+
}
121+
```
122+
123+
**Important**: The trigger character should match the character that triggered the completion:
124+
- For property access (`config.se<cursor>rver`), the trigger character should be "."
125+
- For regular identifier completions (`con<cursor>fig`), you should omit the trigger character
126+
- Using the wrong trigger character can result in irrelevant completions or no completions at all
127+
128+
This was the single most important fix that made property completions work correctly.
129+
130+
131+
### 6. TypeScript Detection
132+
133+
**Challenge**: Needed to detect and use the local project's TypeScript installation.
134+
135+
**Solution**: Checked common installation paths and provided fallbacks:
136+
```javascript
137+
// Check for TypeScript in common locations
138+
const yarnTsPath = path.join(projectPath, '.yarn/sdks/typescript/lib');
139+
const npmTsPath = path.join(projectPath, 'node_modules/typescript/lib');
140+
141+
if (fs.existsSync(yarnTsPath)) {
142+
tsdk = '.yarn/sdks/typescript/lib';
143+
} else if (fs.existsSync(npmTsPath)) {
144+
tsdk = 'node_modules/typescript/lib';
145+
}
146+
```
147+
148+
## Key Implementation Details
149+
150+
### Trigger Kinds
151+
152+
The LSP defines three trigger kinds for completion requests:
153+
1. **Invoked (1)**: Manual completion (Ctrl+Space)
154+
2. **TriggerCharacter (2)**: Automatic completion after typing a specific character
155+
3. **TriggerForIncompleteCompletions (3)**: Request for more items from a previous result
156+
157+
### Trigger Characters
158+
159+
Common trigger characters in TypeScript/JavaScript and when to use them:
160+
161+
| Character | Context | Example | When to Include |
162+
|-----------|---------|---------|----------------|
163+
| `.` | Property access | `object.prop` | When completing properties after a dot |
164+
| `"`, `'` | String literals/imports | `import "./` | When completing paths in quotes |
165+
| `/` | Path completions | `import "./folder/` | When completing folders/files |
166+
| `@` | Decorators | `@Decorator` | When completing TypeScript decorators |
167+
| `<` | JSX tags | `<Component` | When completing JSX/TSX tags |
168+
| `(` | Function calls | `function(` | When completing function parameters |
169+
| `[` | Bracket notation | `object[` | When completing key access |
170+
| (none) | Regular identifiers | `cons` | When completing variable/function names |
171+
172+
**Important**: Do not include a trigger character for regular identifier completions - it will filter results incorrectly. Only include the trigger character that was actually typed by the user to trigger the completion.
173+
174+
## Next Steps
175+
176+
Potential improvements:
177+
1. Add support for document changes (editing files)
178+
2. Implement completion item resolution for more details
179+
3. Add signature help support
180+
4. Add hover information support
181+
5. Improve error recovery and reconnection
182+
6. Add support for multiple files and projects
183+
7. Create a more robust CLI interface with more options
184+
185+
## References
186+
187+
- [Language Server Protocol Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/)
188+
- [TypeScript Language Server](https://github.com/typescript-language-server/typescript-language-server)
189+
- [Zed Editor's TypeScript LSP implementation](https://github.com/zed-industries/zed/blob/148131786f5b15e8fdcb9e550abbf9edfd3dd0f8/crates/project/src/lsp_command.rs#L2035)
File renamed without changes.

0 commit comments

Comments
 (0)