Skip to content

Commit b062c8c

Browse files
authored
Add document how to extend the extension (#34)
The new `README_extension.md` describes how to extend the existing VSCode extension with additional features, which are specific to a third company and should hence not be integrated into the open source project.
1 parent b7f7400 commit b062c8c

File tree

1 file changed

+393
-0
lines changed

1 file changed

+393
-0
lines changed

README_extension.md

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
# Extending the TRLC Extension
2+
3+
## Introduction
4+
5+
This document outlines potential approaches to extend the functionality of the `trlc-vscode-extension` to address specific requirements, such as those of a third company.
6+
Let's call it "Other Company".
7+
Consider the case where "Other Company" has got a requirements engineering process in place that relies on TRLC,
8+
but it has got additional needs regarding the interoperability of TRLC and some other tools,
9+
For examples, "Other Company" might require that
10+
the VSCode extension for TRLC is enriched with information specific to "Other Company".
11+
Imagine their process defines a TRLC attribute called `Jira_ID`, which is the key of a Jira ticket given as `String`.
12+
The VSCode extension shall be able to understand this `String`.
13+
Whenever the user hovers over it with the cursor, the IDE shall offer to open that Jira ticket in a web browser window.
14+
15+
This use case is very specific to "Other Company".
16+
17+
In this document we outline how to develop an extension for Visual Studio Code that adds such a feature to the existing TRLC extension.
18+
19+
This document explores two options:
20+
1. to create a fork of the extension specific to "Other Company", and
21+
2. to implement a plugin system through a second VSCode extension.
22+
23+
On both approaches the changes to the existing architecture are addressed to enable the functionalities.
24+
In the second option modifying the core extension was minimized as much as possible by suggesting an elegant solution for it: *"exposing its data through its own `API`"*.
25+
26+
The second approach is the recommended approach, and we sketch the first approach only briefly.
27+
28+
## Context
29+
30+
The `trlc-vscode-extension` uses a so-called “Language Server Protocol” (LSP) as a wrapper around the TRLC api.
31+
With this server, the extension provides features like syntax checking for `.rsl` and `.trlc` files directly in VSCode.
32+
The heart of the VSCode extension uses an open-source tool called [**Pygls**](https://github.com/openlawlibrary/pygls).
33+
34+
The Language Server Protocol (LSP) is a standard created by Microsoft to simplify adding language features like auto-completion, syntax checking, and error diagnostics to any code editor.
35+
It separates the editor's user interface (client) from language-specific logic (server),
36+
enabling reusability of the same server across multiple editors.
37+
38+
## Current Architecture
39+
40+
The extension follows a client-server model,
41+
utilizing the LSP to separate the editor's interface (client) from the language-specific logic (server).
42+
The server is implemented in Python using the Pygls library, while the client is developed in TypeScript.
43+
44+
The client is implemented [here](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/main/client/src/extension.ts).
45+
It handles the communication between the VSCode editor and the Python-based server.
46+
47+
The server is implemented [here](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/main/server/__main__.py).
48+
It performs all the heavy lifting for parsing and analyzing `.rsl` and `.trlc` files.
49+
50+
The LSP operates on a client-server mode:
51+
- The language server (provided by `pygls`) offers features like hover, diagnostics, and completion.
52+
- The VSCode client sends requests (e.g., hover requests) and displays responses.
53+
54+
### Possible Approaches
55+
We now go into the details of the two possible approaches to deal with specific needs of "Other Company".
56+
57+
### 1. Fork of the Extension
58+
A "Other Company" fork of the extension provides complete control over the codebase but comes with the downside of maintaining and synchronizing changes with the upstream open-source repository.
59+
60+
- New handlers in `server/server.py` are needed to be added for specific checks of "Other Company".
61+
- Full control of current file parsing/processing as a post-processing step is given, and new handlers within existing parsing can be added.
62+
- No API needs to be exposed nor implemented.
63+
64+
The code below illustrates how to add features specific to "Other Company" to the code.
65+
66+
```python
67+
class OtherCompanySpecificChecks:
68+
def __init__(self, server):
69+
"""
70+
Initialize the handler specific to the needs of 'Other Company'.
71+
"""
72+
self.server = server
73+
74+
async def fetch_jira_preview(self, jira_id):
75+
# Simulate async API call for sneak preview
76+
print(f"Fetching Jira preview for ID: {jira_id}")
77+
78+
def validate_signals(self, uri, content):
79+
# Add signal parsing and validation logic here
80+
print(f"Validating signals in: {uri}")
81+
82+
def validate_asil_levels(self, uri, content):
83+
# Add ASIL validation logic here
84+
print(f"Validating ASIL levels in: {uri}")
85+
86+
def pretty_print(self, uri):
87+
# Add pretty-printing logic here
88+
print(f"Pretty-printing {uri}")
89+
```
90+
91+
This code has to be made known to `TrlcLanguageServer` in [server.py](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/main/server/server.py).
92+
Create a new instance in its `__init__` function:
93+
```python
94+
def __init__(self, *args):
95+
# ...
96+
self.other_company_checks = OtherCompanySpecificChecks(self)
97+
# ...
98+
```
99+
100+
Then extend its [validate](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/b7f74006af0f1e17dd1171af82b0b3bc4f1f7de9/server/server.py#L105) function:
101+
```python
102+
def validate(self):
103+
# ..
104+
for uri, document in self.workspace.documents.items():
105+
if uri.endswith('.trlc'):
106+
print(f"Post-parsing checks for: {uri}")
107+
108+
# Call "Other Company" specific methods
109+
self.other_company_checks.validate_signals(uri, document.source)
110+
self.other_company_checks.validate_asil_levels(uri, document.source)
111+
# more methods...
112+
```
113+
114+
Also update function [did_change](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/b7f74006af0f1e17dd1171af82b0b3bc4f1f7de9/server/server.py#L269):
115+
```python
116+
@trlc_server.feature(TEXT_DOCUMENT_DID_CHANGE)
117+
async def did_change(ls, params: DidChangeTextDocumentParams):
118+
# ...
119+
# Perform post-parsing checks specific to "Other Company"
120+
if uri.endswith('.trlc'):
121+
ls.other_company_checks.validate_signals(uri, content)
122+
ls.other_company_checks.validate_asil_levels(uri, content)
123+
# MORE FEATURES...
124+
125+
# Publish diagnostics after company-specific checks
126+
ls.publish_diagnostics(uri, [])
127+
```
128+
129+
_Disclaimer_: The changes presented above try to point into the most probable code positions and functions to be edited to make specific enhancements.
130+
131+
### 2. Plugin System for Extensibility
132+
The second (and recommended) option is to enhance the existing TRLC extension to expose an API, which can then be accessed by any other VSCode extension.
133+
"Other Company" would then develop its own VSCode extension and simply access that API.
134+
135+
This allows isolating almost completely the original `vscode-trlc-extension`.
136+
"Other Company" specific logic would be packaged into a new `vscode-trlc-other-company-expansion-extension` that the original open-source extension dynamically feeds through a yet-to-be implemented API.
137+
This avoids modifying the core extension, but requires compatibility management.
138+
And depending on the complexity of the future features this could require its own LSP.
139+
At the moment of writing this document and the imagined use case,
140+
a "*client-only*" extension seems to be enough to fulfill the task.
141+
Nonetheless, a second LSP would provide advanced language processing possibilities for "Other Company".
142+
143+
To enable communication with the current TRLC extension,
144+
the current client needs to expose an API.
145+
And currently this is **not the case**, but a proper API could be built in [extension.ts](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/main/client/src/extension.ts).
146+
Microsoft offers examples on this topic, see [VSCode API](https://code.visualstudio.com/api/references/vscode-api#extensions:~:text=open%20was%20successful.-,extensions,-Namespace%20for%20dealing).
147+
148+
### API Example
149+
150+
In the current `extension.ts`, the TRLC data should be exposed in the `activate` function as follows:
151+
152+
```javascript
153+
export function activate(context: vscode.ExtensionContext) {
154+
let api = {
155+
sum(a, b) {
156+
return a + b;
157+
},
158+
mul(a, b) {
159+
return a * b;
160+
}
161+
};
162+
// 'export' public api-surface
163+
return api;
164+
}
165+
```
166+
167+
## Implementation Outline
168+
169+
This section outlines the implementation.
170+
171+
1. Expand the current client to expose an API with the relevant data.
172+
All TRLC objects must be returned by the API.
173+
2. The new plug-in extension for "Other Company" must load after the TRLC extension has loaded.
174+
Establish this dependency via the file `package.json` in your new `vscode-trlc-other-company-expansion-extension`:
175+
```json
176+
{
177+
"extensionDependencies": ["bmw.trlc-vscode-extension"]
178+
}
179+
```
180+
181+
### Details
182+
In the TRLC extension, edit `client/src/extension.ts` to expose a function like `getHoverDetails(uri: string, position: Position)` in its `activate` function,
183+
which sends a `textDocument/hover` request to the language server via `client.sendRequest` and returns hover data.
184+
185+
So at the end of [activate](https://github.com/bmw-software-engineering/trlc-vscode-extension/blob/b7f74006af0f1e17dd1171af82b0b3bc4f1f7de9/client/src/extension.ts#L139) insert this piece of code:
186+
```typescript
187+
// Expose API when hovering - this makes functions like "getHoverDetails" etc. available to other extensions
188+
const api = {
189+
async getHoverDetails(uri: string, position: Position): Promise<Hover | null> {
190+
const params = { textDocument: { uri }, position };
191+
return await client.sendRequest("textDocument/hover", params);
192+
},
193+
async getTrlcObject(uri: string, position: Position): Promise<CBIDDetails | null> {
194+
// Placeholder: Implement logic to retrieve TRLC object at curser position
195+
},
196+
// add more exposures as needed
197+
};
198+
199+
// Export the API to make it available to other extensions
200+
// and terminate properly when done
201+
// see: https://stackoverflow.com/questions/55554018/purpose-for-subscribing-a-command-in-vscode-extension
202+
context.subscriptions.push(client);
203+
context.exports = api;
204+
return api;
205+
```
206+
207+
In the language server (`server/server.py`), ensure the `hover` function,
208+
registered with `@trlc_server.feature(TEXT_DOCUMENT_HOVER)`,
209+
processes requests by identifying tokens under the cursor and returns relevant details in a `Hover` object.
210+
In other words, within `server.py`, the hover function decorated with
211+
`@trlc_server.feature(TEXT_DOCUMENT_HOVER)`
212+
intercepts the `textDocument/hover` request from `extension.ts` (the client).
213+
Finally, use `context.subscriptions` for proper lifecycle management (ending) and assign the extended API object to `context.exports` to make it accessible to any other extension.
214+
215+
In the new extension (`other-company-extension.ts`), use the exposed API by accessing `trlc-vscode-extension` via `vscode.extensions.getExtension`,
216+
calling `getHoverDetails` to fetch hover data,
217+
and enhancing functionality, such as formatting text using `TextEditorDecorationType` [see here](https://code.visualstudio.com/api/references/vscode-api#window.createTextEditorDecorationType:~:text=createTextEditorDecorationType(options%3A%20DecorationRenderOptions)%3A%20TextEditorDecorationType) or making API requests to external services like Jira.
218+
219+
`other-company-extension.ts` could look like this:
220+
```typescript
221+
import * as vscode from 'vscode';
222+
223+
export function activate(context: vscode.ExtensionContext) {
224+
// very important step to set up dependency to TRLC extension!
225+
// do not forget also to set it up in package.json
226+
const trlcExtension = vscode.extensions.getExtension('trlc.vscode-extension');
227+
228+
if (trlcExtension) {
229+
// - set formating and/or decortations for relevant properties like Jira ID etc
230+
// - call web browser to open a webview preview with the Jira ticket
231+
// - call an external Python script called 'sanity checks'
232+
trlcExtension.activate().then((api) => {
233+
if (api) {
234+
applyDecorations(api);
235+
fetchJiraDetails(api);
236+
setupSanityChecks(context, api);
237+
}
238+
}).catch((error) => {
239+
console.error("Failed to activate trlc-vscode-extension:", error);
240+
});
241+
} else {
242+
console.warn("trlc-vscode-extension not found.");
243+
}
244+
}
245+
246+
247+
function applyDecorations(api) {
248+
// Format in UI any SYML and CodeBeamer elements with decorations.
249+
// Use the original extension's API (`api.getHoverDetails`) for example
250+
// to fetch hover details and enhance UI.
251+
}
252+
253+
function fetchJiraDetails(api) {
254+
// Trigger an API request to JIra and display the result.
255+
}
256+
257+
258+
function setupSanityChecks(context: vscode.ExtensionContext, api: any) {
259+
// Set up triggers for running sanity checks, e.g., upon saving the file
260+
// if condition is met, then run "runSanityChecks" with the
261+
// worskpace path as argument for example
262+
runSanityChecks(vscode.workspace.workspaceFolders);
263+
264+
}
265+
266+
function runSanityChecks(workspaceFolders: readonly vscode.WorkspaceFolder[] | undefined) {
267+
const workspacePath = workspaceFolders;
268+
const scriptPath = '/path/to/sanity_checks.py';
269+
270+
// run the CLI Tool with python from client
271+
const command = `python ${scriptPath} "${workspacePath}"`;
272+
273+
// now adjust UI to show up errors or warnings
274+
// ...
275+
}
276+
```
277+
278+
### Use Case Suggestions
279+
280+
Independently of the approach (fork or extension) the following suggestions apply.
281+
282+
All the following suggestions are thought to be implemented on the client's side in `TypeScript` and it does not mean that these cannot be done on the server's side.
283+
They are just focused on minimizing changes to the original codebase and to avoid the development of a custom Language Server Protocol (LSP).
284+
It is just a possible design approach/suggestion.
285+
286+
#### 1. Web Preview from Jira
287+
288+
In order to show the contents of a Jira ticket (when hoovered or any other desired trigger),
289+
we would need to make an HTTP request to the Jira server. If done with server, expose html contents through the API or if done with client handle the request with Node.js directly.
290+
In either way the response has to be "rendered" properly into VSCode.
291+
292+
You can use `Webviews` to create a browser-like rendering environment, see [createWebviewPanel](https://code.visualstudio.com/api/references/vscode-api)
293+
294+
This is how the code could look like:
295+
296+
```javascript
297+
// client's side
298+
export function activate(context: vscode.ExtensionContext) {
299+
let disposable = vscode.commands.registerCommand('extension.fetchAndRenderHtml', async () => {
300+
const url = 'https://example.com';
301+
302+
try {
303+
// handle log in
304+
// Fetch webpage's content
305+
const response = await fetch(url);
306+
307+
// Create and show a new Webview panel
308+
const panel = vscode.window.createWebviewPanel(
309+
'htmlPreview', // Identifier
310+
'HTML Preview', // Title
311+
// other properties if needed....
312+
);
313+
314+
// Set the HTML content of the Webview
315+
panel.webview.html = html;
316+
} catch (err) {
317+
vscode.window.showErrorMessage(`Error fetching data: ${err.message}`);
318+
}
319+
});
320+
321+
context.subscriptions.push(disposable);
322+
}
323+
324+
```
325+
326+
The previous suggestion is assuming a simple command identifier `fetchAndRenderHtml` that could be programmatically triggered.
327+
But the proper event listener must yet be at best defined,
328+
like hover over a TRLC object or clicking it.
329+
330+
#### 2. Pretty-printing TRLC files
331+
332+
Maybe "Other Company" decided to allow CommonMark in the `string` of a TRLC value.
333+
To nicely print that there is an open source tool called [`markdown-it`](https://github.com/markdown-it/markdown-it).
334+
It is implemented in `Node.js` and therefore can be integrated into the client inside VSCode.
335+
It can interpret text formatted as common mark, like: `\*\*Some string in bold\*\*` could be rendered to **Some string in bold**.
336+
This, of course, implies **first** to adjust/edit the desired strings before giving those to `markdown-it`.
337+
View a demo [here](https://markdown-it.github.io/).
338+
339+
#### 3. Highlighting External References
340+
341+
If the extension of "Other Company" shall apply additional styling to the TRLC files, then most probably the `TextEditorDecorationType` can be used.
342+
343+
Here is a basic example:
344+
```javascript
345+
import * as vscode from 'vscode';
346+
347+
export function activate(context: vscode.ExtensionContext) {
348+
// Set up a trigger, like when file is opened.
349+
// set some styling
350+
const redText = vscode.window.createTextEditorDecorationType({ color: 'red' });
351+
const blueText = vscode.window.createTextEditorDecorationType({ color: 'blue' });
352+
const boldText = vscode.window.createTextEditorDecorationType({ fontWeight: 'bold' });
353+
354+
// Regex to match the pattern {{OTHERCOMPANY:placeholder}}
355+
const regex = /\{\{(OTHERCOMPANY):([^}]+)\}\}/g;
356+
357+
// Get text and parse it
358+
// ...
359+
// Find matches and calculate ranges
360+
// ...
361+
// Apply decorations to the editor, very similar to:
362+
editor.setDecorations(redText, redRanges);
363+
editor.setDecorations(blueText, blueRanges);
364+
editor.setDecorations(boldText, boldRanges);
365+
});
366+
367+
// more code...
368+
}
369+
```
370+
371+
For more details visit Microsoft's support page in section [createTextEditorDecorationType](https://code.visualstudio.com/api/references/vscode-api#window.createTextEditorDecorationType) and its related instances.
372+
373+
Also see the official node.js documentation [child_process](https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback)
374+
375+
### Conclusion
376+
377+
The fork approach grants direct control over the data at parsing time, making it an attractive option for a quicker solution.
378+
However, in the long term, it creates a higher burden in terms of synchronization and maintenance.
379+
Maintaining a fork may require continuous updates to ensure compatibility with upstream changes,
380+
which can result in significant overhead.
381+
382+
On the other hand, implementing a plugin system via a second extension offers a decoupled solution.
383+
By exposing an API of the original `trlc-vscode-extension`,
384+
the second extension can just dynamically consume and extend functionality without tightly coupling with the core extension.
385+
Please consider that "catching" and "exposing" the data may be tricky.
386+
This second approach involves a steeper initial development effort, requiring both architectural adjustments to the existing extension, yet minimal, and careful planning to ensure API reliability.
387+
Additionally, the actual need for a second Language Server Protocol (LSP) must be carefully evaluated.
388+
389+
### Useful Links
390+
391+
- [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/)
392+
- [Pygls GitHub](https://github.com/openlawlibrary/pygls)
393+
- [VSCode Extension Anatomy](https://code.visualstudio.com/api/get-started/extension-anatomy)

0 commit comments

Comments
 (0)