This document explains the architecture and interaction between the Language Server and IDEs for rendering the UI components. It aims to guide contributors in adding or modifying UI components.
Our approach unifies the user interface for Snyk Code Ignores across multiple IDEs by leveraging the Language Server Protocol. This ensures consistency, reduces redundancy, and simplifies future enhancements.
sequenceDiagram
participant LSP
participant Template
participant IDE
LSP->>Template: Generate HTML Template
Template->>IDE: Send HTML Template
IDE->>Template: Inject IDE-specific CSS
IDE->>User: Rendered Content
- Generate HTML Template: The Language Server generates an HTML template using the Go
html/template
library. The template includes placeholders for dynamic content. - Send HTML Template: The generated HTML template is sent to the IDEs. This template includes the structure of the UI components but lacks specific styling.
- Injected IDE-specific CSS: Each IDE injects its own CSS to style the HTML template according to its theming and design guidelines. This allows the same HTML structure to be visually consistent with the rest of the IDE.
This directive loads static assets like CSS and HTML files directly into our application. This is particularly useful for embedding resources such as templates and stylesheets that are needed for rendering the UI components.
//go:embed template/index.html
var detailsHtmlTemplate string
//go:embed template/styles.css
var stylesCSS string
The Go build system will recognize the directives and arrange for the declared variable to be populated with the matching files from the file system.
To add or modify UI components in the IDEs that support the Language Server, follow these steps:
- Process the dynamic data to be rendered in the HTML template in
infrastructure/code/code_html.go
. For example, to display the issue ecosystem in the UI:
func getCodeDetailsHtml(issue snyk.Issue) string {
c := config.CurrentConfig()
data := map[string]interface{}{
"Ecosystem": issue.Ecosystem,
"IssueTitle": additionalData.Title,
// more data
}
var html bytes.Buffer
if err := globalTemplate.Execute(&html, data); err != nil {
c.Logger().Error().Msgf("Failed to execute main details template: %v", err)
return ""
}
return html.String()
}
- Update the HTML template in
infrastructure/code/template/details.html
to include the placeholders for the dynamic data. Include CSS in the template itself if the styles are the same across all IDEs, otherwise use IDE-specific files.
<head>
<style>
.ecosystem-badge {
padding: 0.35em 0.35em;
border-radius: 0.25em;
margin-left: 1em;
background: #FFF4ED;
color: #B6540B;
border: 1px solid #E27122;
}
</style>
</head>
<body>
<h2 class="severity-title">{{.IssueTitle}}</h2>
<span class="delimiter"></span>
<div>Priority score: {{.PriorityScore}}</div>
<span class="delimiter"></span>
<div class="ecosystem-badge">{{.Ecosystem}}</div>
<!-- more HTML -->
</body>
- If necessary, add IDE-specific CSS:
VSCode
- CSS: suggestionLS.scss
- HTML Rendering: codeSuggestionWebviewProvider.ts
- Script Injection: codeSuggestionWebviewScriptLS.ts
IntelliJ
-
HTML Rendering: JCEFDescriptionPanel.kt
- Handle Nonce and IDE-Specific Styles
When dealing with Content Security Policies (CSP) in the HTML generated by the Language Server, it’s important to correctly handle nonce
attributes for both styles and scripts to ensure they are applied securely.
- Language Server: Ensure the HTML template includes
nonce
placeholders that will be replaced by dynamically generated nonces. For example:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src 'self' 'nonce-{{.Nonce}}' 'nonce-ideNonce'; script-src 'nonce-{{.Nonce}}';">
<!--noformat-->
<style nonce="{{.Nonce}}">
{{.Styles}}
</style>
<!--noformat-->
<style nonce="ideNonce" data-ide-style></style>
</head>
-
{{.Nonce}}
: This is a dynamically generated nonce passed from, for example,infrastructure/iac/iac_html.go
. -
{{.Styles}}
: This is where the styles defined in the Language Server are injected, also passed viainfrastructure/iac/iac_html.go
. -
ideNonce
: This placeholder will be replaced by the IDE with its own dynamically generated nonce for IDE-specific styles. -
IDE (e.g., VSCode): Replace the nonce placeholder in the HTML with the actual nonce generated by the IDE, and inject IDE-specific styles:
private getHtmlFromLanguageServer(html: string): string {
const nonce = getNonce();
const ideStylePath = vscode.Uri.joinPath(
vscode.Uri.file(this.context.extensionPath),
'media',
'views',
'snykCode',
'suggestion',
'suggestionLS.css',
);
const ideStyle = readFileSync(ideStylePath.fsPath, 'utf8');
html = html.replace(/nonce-ideNonce/g, `nonce-${nonce}`); // Replace the placeholder with IDE nonce
html = html.replace(
'<style nonce="ideNonce" data-ide-style></style>',
`<style nonce="${nonce}">${ideStyle}</style>`,
);
return html;
}
- Generate HTML Template with Nonce: The Language Server generates an HTML template, including placeholders for
nonce
attributes. - Send HTML Template to IDE: The IDE receives the template and prepares to render it.
- Replace Nonce and Inject Styles: The IDE replaces the nonce placeholders with actual nonces and injects any IDE-specific styles.