Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/brave-cities-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hashicorp/design-system-components": minor
---

<!-- START components/code-editor -->
`CodeEditor` - Added a new `@customExtensions` argument which allows consumers to provide their own custom CodeMirror extensions.
<!-- END -->
6 changes: 5 additions & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,11 @@
"types": "./declarations/*.d.ts",
"default": "./dist/*"
},
"./addon-main.js": "./addon-main.cjs"
"./addon-main.js": "./addon-main.cjs",
"./codemirror": {
"types": "./declarations/codemirror.d.ts",
"default": "./dist/codemirror.js"
}
},
"typesVersions": {
"*": {
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/codemirror.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from '@codemirror/state';
export * from '@codemirror/view';
export * from '@codemirror/commands';
export * from '@codemirror/language';
export * from '@codemirror/lint';
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
ariaDescribedBy=this.ariaDescribedBy
ariaLabel=@ariaLabel
ariaLabelledBy=this.ariaLabelledBy
customExtensions=@customExtensions
cspNonce=@cspNonce
extraKeys=@extraKeys
hasLineWrapping=@hasLineWrapping
Expand Down
30 changes: 21 additions & 9 deletions packages/components/src/modifiers/hds-code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type HdsCodeEditorBlurHandler = (
) => void;

interface HdsCodeEditorExtraKeys {
[key: string]: () => void;
[key: string]: () => boolean;
}

export interface HdsCodeEditorSignature {
Expand All @@ -53,6 +53,7 @@ export interface HdsCodeEditorSignature {
ariaLabelledBy?: string;
cspNonce?: string;
extraKeys?: HdsCodeEditorExtraKeys;
customExtensions?: Extension[];
hasLineWrapping?: boolean;
isLintingEnabled?: boolean;
language?: HdsCodeEditorLanguages;
Expand Down Expand Up @@ -397,8 +398,18 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
language,
hasLineWrapping,
isLintingEnabled,
customExtensions,
onLint,
}) => {
}: Pick<
HdsCodeEditorSignature['Args']['Named'],
| 'cspNonce'
| 'extraKeys'
| 'language'
| 'hasLineWrapping'
| 'isLintingEnabled'
| 'customExtensions'
| 'onLint'
>) => {
const [
{
keymap,
Expand All @@ -417,11 +428,8 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu

const languageExtensions = await this._loadLanguageExtensionsTask.perform(
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
language,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
isLintingEnabled,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
onLint,
}
);
Expand All @@ -448,7 +456,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
hasLineWrapping ? EditorView.lineWrapping : []
);

let extensions = [
let extensions: Extension[] = [
lineWrappingExtension,
bracketMatching(),
highlightActiveLine(),
Expand All @@ -458,13 +466,14 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
keymap.of([...defaultKeymap, ...historyKeymap]),
// custom extensions
handleUpdateExtension,
// user-provided extensions
...(customExtensions ?? []),
// hds dark theme
hdsDarkTheme,
syntaxHighlighting(hdsDarkHighlightStyle),
];

if (extraKeys !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const customKeyMap = Object.entries(extraKeys).map(([key, value]) => ({
key: key,
run: value,
Expand All @@ -478,11 +487,9 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
}

// add nonce to the editor view if it exists
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const nonce = cspNonce ?? getCSPNonceFromMeta();

if (nonce !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
extensions = [...extensions, EditorView.cspNonce.of(nonce)];
}

Expand All @@ -504,6 +511,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
value,
hasLineWrapping,
isLintingEnabled,
customExtensions,
onLint,
}: Pick<
HdsCodeEditorSignature['Args']['Named'],
Expand All @@ -513,6 +521,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
| 'value'
| 'hasLineWrapping'
| 'isLintingEnabled'
| 'customExtensions'
| 'onLint'
>
) => {
Expand All @@ -525,6 +534,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
language,
hasLineWrapping: hasLineWrapping ?? false,
isLintingEnabled,
customExtensions,
onLint,
});

Expand Down Expand Up @@ -592,6 +602,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
isLintingEnabled,
language,
value,
customExtensions,
} = named;

this.onInput = onInput;
Expand All @@ -610,6 +621,7 @@ export default class HdsCodeEditorModifier extends Modifier<HdsCodeEditorSignatu
extraKeys,
language,
value,
customExtensions,
});

if (editor === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SubSectionStandalone from 'showcase/components/page-components/code-edito
import SubSectionHeader from 'showcase/components/page-components/code-editor/sub-sections/header';
import SubSectionSyntaxHighlighting from 'showcase/components/page-components/code-editor/sub-sections/syntax-highlighting';
import SubSectionLinting from 'showcase/components/page-components/code-editor/sub-sections/linting';
import SubSectionCustomExtension from 'showcase/components/page-components/code-editor/sub-sections/custom-extension';
import SubSectionStandaloneModifier from 'showcase/components/page-components/code-editor/sub-sections/standalone-modifier';

const CodeEditorIndex: TemplateOnlyComponent = <template>
Expand All @@ -25,6 +26,7 @@ const CodeEditorIndex: TemplateOnlyComponent = <template>
<SubSectionHeader />
<SubSectionSyntaxHighlighting />
<SubSectionLinting />
<SubSectionCustomExtension />
<SubSectionStandaloneModifier />
</section>
</template>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import { keymap } from '@hashicorp/design-system-components/codemirror';

import ShwTextH2 from 'showcase/components/shw/text/h2';

import { HdsCodeEditor } from '@hashicorp/design-system-components/components';

import type { EditorView } from '@hashicorp/design-system-components/codemirror';

const insertCodeSnippet = (view: EditorView) => {
view.dispatch({
changes: {
from: view.state.selection.main.head,
insert: '// This is a custom inserted snippet\n',
},
});

return true;
};

const codeSnippetKeymap = keymap.of([
{
key: 'Ctrl-Shift-h',
run: insertCodeSnippet,
},
]);

export default class SubSectionCustomExtension extends Component {
customExtensions = [codeSnippetKeymap];

<template>
<ShwTextH2>Custom Extension</ShwTextH2>

<HdsCodeEditor @customExtensions={{this.customExtensions}} as |CE|>
<CE.Title>Code editor with custom extension</CE.Title>
<CE.Description>
This extension will listen for a specific key combination (<code
>Ctrl-Shift-h</code>
) and insert a predefined snippet of code at the current cursor
position.
</CE.Description>
</HdsCodeEditor>
</template>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
import type { Diagnostic as DiagnosticType } from '@codemirror/lint';
import type { Diagnostic as DiagnosticType } from '@hashicorp/design-system-components/codemirror';

import ShwTextH2 from 'showcase/components/shw/text/h2';
import ShwFlex from 'showcase/components/shw/flex';
Expand Down
28 changes: 27 additions & 1 deletion showcase/tests/integration/modifiers/hds-code-editor-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { EditorView as EditorViewType } from '@codemirror/view';
import type { Diagnostic as DiagnosticType } from '@codemirror/lint';

import hdsCodeEditor from '@hashicorp/design-system-components/modifiers/hds-code-editor';
import { EditorView } from '@hashicorp/design-system-components/codemirror';
import type { HdsCodeEditorSignature } from '@hashicorp/design-system-components/modifiers/hds-code-editor';

import { setupRenderingTest } from 'showcase/tests/helpers';
Expand All @@ -39,6 +40,7 @@ const createCodeEditor = async (options: {
language?: HdsCodeEditorSignature['Args']['Named']['language'];
isLintingEnabled?: HdsCodeEditorSignature['Args']['Named']['isLintingEnabled'];
cspNonce?: HdsCodeEditorSignature['Args']['Named']['cspNonce'];
customExtensions?: HdsCodeEditorSignature['Args']['Named']['customExtensions'];
}) => {
return await render(
<template>
Expand All @@ -56,6 +58,7 @@ const createCodeEditor = async (options: {
language=options.language
isLintingEnabled=options.isLintingEnabled
cspNonce=options.cspNonce
customExtensions=options.customExtensions
}}
/>
</template>,
Expand Down Expand Up @@ -312,7 +315,7 @@ module('Integration | Modifier | hds-code-editor', function (hooks) {

// extraKeys
test('setting extraKeys should add the provided keybindings to the editor', async function (assert) {
const saveSpy = sinon.spy(() => console.log('Save!'));
const saveSpy = sinon.spy(() => true);

const extraKeys = {
'Shift-Enter': saveSpy,
Expand All @@ -335,6 +338,29 @@ module('Integration | Modifier | hds-code-editor', function (hooks) {
assert.ok(saveSpy.calledOnce);
});

// customExtensions
test('it should load custom extensions provided via the customExtensions argument', async function (assert) {
const customClassName = 'my-custom-test-class';

// create a simple extension that adds a specific class to the editor's wrapper element.
const myTestClassExtension = EditorView.editorAttributes.of({
class: customClassName,
});

await createCodeEditor({
ariaLabel: 'test with custom extension',
customExtensions: [myTestClassExtension],
});

await waitFor('.cm-editor');
assert
.dom('.cm-editor')
.hasClass(
customClassName,
'the custom extension successfully injected the class attribute',
);
});

// ASSERTIONS

test('it should throw an assertion if both ariaLabel and ariaLabelledBy are ommitted', async function (assert) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { module, test } from 'qunit';
import { Text } from '@codemirror/state';
import { Text } from '@hashicorp/design-system-components/codemirror';
import {
findNextToken,
determineErrorMessage,
Expand Down
Loading