Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
terminal-component.ts17.2 kB
import { html, LitElement, TemplateResult, css } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { Terminal } from '@xterm/xterm'; import { FitAddon } from '@xterm/addon-fit'; @customElement('terminal-component') export class TerminalComponent extends LitElement { static override styles = css` :host { display: block; width: 100%; height: 100%; min-height: 300px; background-color: var(--vscode-panel-background, #1e1e1e); color: var(--vscode-terminal-foreground, #cccccc); font-family: var( --vscode-editor-font-family, 'Menlo', 'Monaco', 'Courier New', monospace ) !important; font-size: var(--vscode-editor-font-size) !important; } .xterm-rows { font-size: var(--vscode-editor-font-size) !important; } #terminal-container { width: 100%; height: 100%; overflow: hidden; position: relative; padding: 10px 20px; } .xterm { height: 100%; cursor: text; position: relative; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .xterm.focus, .xterm:focus { outline: none; } .xterm .xterm-helpers { position: absolute; top: 0; /** * The z-index of the helpers must be higher than the canvases in order for * IMEs to appear on top. */ z-index: 5; } .xterm .xterm-helper-textarea { padding: 0; border: 0; margin: 0; /* Move textarea out of the screen to the far left, so that the cursor is not visible */ position: absolute; opacity: 0; left: -9999em; top: 0; width: 0; height: 0; z-index: -5; /** Prevent wrapping so the IME appears against the textarea at the correct position */ white-space: nowrap; overflow: hidden; resize: none; } .xterm .composition-view { /* TODO: Composition position got messed up somewhere */ background: #000; color: #fff; display: none; position: absolute; white-space: nowrap; z-index: 1; } .xterm .composition-view.active { display: block; } .xterm .xterm-viewport { /* On OS X this is required in order for the scroll bar to appear fully opaque */ background-color: #000; overflow-y: scroll; cursor: default; position: absolute; right: 0; left: 0; top: 0; bottom: 0; height: 100% !important; } .xterm .xterm-screen { position: relative; height: 100%; } .xterm .xterm-screen canvas { position: absolute; left: 0; top: 0; } .xterm-char-measure-element { display: inline-block; visibility: hidden; position: absolute; top: 0; left: -9999em; line-height: normal; } .xterm.enable-mouse-events { /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ cursor: default; } .xterm.xterm-cursor-pointer, .xterm .xterm-cursor-pointer { cursor: pointer; } .xterm.column-select.focus { /* Column selection mode */ cursor: crosshair; } .xterm .xterm-accessibility:not(.debug), .xterm .xterm-message { position: absolute; left: 0; top: 0; bottom: 0; right: 0; z-index: 10; color: transparent; pointer-events: none; } .xterm .xterm-accessibility-tree:not(.debug) *::selection { color: transparent; } .xterm .xterm-accessibility-tree { font-family: monospace; user-select: text; white-space: pre; } .xterm .xterm-accessibility-tree > div { transform-origin: left; width: fit-content; } .xterm .live-region { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; } .xterm-dim { /* Dim should not apply to background, so the opacity of the foreground color is applied * explicitly in the generated class and reset to 1 here */ opacity: 1 !important; } .xterm-underline-1 { text-decoration: underline; } .xterm-underline-2 { text-decoration: double underline; } .xterm-underline-3 { text-decoration: wavy underline; } .xterm-underline-4 { text-decoration: dotted underline; } .xterm-underline-5 { text-decoration: dashed underline; } .xterm-overline { text-decoration: overline; } .xterm-overline.xterm-underline-1 { text-decoration: overline underline; } .xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } .xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } .xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } .xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } .xterm-strikethrough { text-decoration: line-through; } .xterm-screen .xterm-decoration-container .xterm-decoration { z-index: 6; position: absolute; } .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { z-index: 7; } .xterm-decoration-overview-ruler { z-index: 8; position: absolute; top: 0; right: 0; pointer-events: none; } .xterm-decoration-top { z-index: 2; position: relative; } /* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */ /* xterm.js customization: Override xterm's cursor style */ .xterm .xterm-scrollable-element > .scrollbar { cursor: default; } /* Arrows */ .xterm .xterm-scrollable-element > .scrollbar > .scra { cursor: pointer; font-size: 11px !important; } .xterm .xterm-scrollable-element > .visible { opacity: 1; /* Background rule added for IE9 - to allow clicks on dom node */ background: rgba(0, 0, 0, 0); transition: opacity 100ms linear; /* In front of peek view */ z-index: 11; } .xterm .xterm-scrollable-element > .invisible { opacity: 0; pointer-events: none; } .xterm .xterm-scrollable-element > .invisible.fade { transition: opacity 800ms linear; } /* Scrollable Content Inset Shadow */ .xterm .xterm-scrollable-element > .shadow { position: absolute; display: none; } .xterm .xterm-scrollable-element > .shadow.top { display: block; top: 0; left: 3px; height: 3px; width: 100%; box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset; } .xterm .xterm-scrollable-element > .shadow.left { display: block; top: 3px; left: 0; height: 100%; width: 3px; box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; } .xterm .xterm-scrollable-element > .shadow.top-left-corner { display: block; top: 0; left: 0; height: 3px; width: 3px; } .xterm .xterm-scrollable-element > .shadow.top.left { box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; } .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: var(--vscode-scrollbarSlider-activeBackground, #5a5a5a); } /* Hide the cursor */ .xterm .xterm-cursor, .xterm .xterm-cursor-outline, .xterm .xterm-cursor-block { display: none !important; visibility: hidden !important; } `; @property({ type: String }) content = ''; @property({ type: Number }) rows = 20; @property({ type: Number }) cols = 80; private terminal: Terminal | null = null; private fitAddon: FitAddon | null = null; private resizeObserver: ResizeObserver | null = null; private themeObserver: MutationObserver | null = null; override render(): TemplateResult { return html`<div id="terminal-container"></div>`; } override firstUpdated(): void { this.initializeTerminal(); } override updated(changedProperties: Map<string, any>): void { if (changedProperties.has('content') && this.terminal) { this.updateContent(); } } override disconnectedCallback(): void { super.disconnectedCallback(); if (this.terminal) { this.terminal.dispose(); } if (this.resizeObserver) { this.resizeObserver.disconnect(); } if (this.themeObserver) { this.themeObserver.disconnect(); } } private initializeTerminal(): void { const container = this.shadowRoot?.getElementById('terminal-container'); if (!container) return; // Create terminal with theme this.createTerminalWithTheme(); this.terminal!.open(container); this.setupResizeObserver(); this.setupThemeObserver(); // Initial fit to ensure proper sizing setTimeout(() => { this.fitTerminal(); // Ensure content is written after terminal is fully initialized if (this.content) { this.updateContent(); } }, 100); } private createTerminalWithTheme(): void { const isDarkTheme = this.isDarkTheme(); const theme = isDarkTheme ? this.getDarkTheme() : this.getLightTheme(); this.terminal = new Terminal({ theme: theme, rows: this.rows, cols: this.cols, lineHeight: 1.5, scrollback: 5000, convertEol: true, cursorBlink: false, cursorStyle: 'block', cursorInactiveStyle: 'none', }); this.fitAddon = new FitAddon(); this.terminal.loadAddon(this.fitAddon); } private isDarkTheme(): boolean { // Check VS Code theme by looking at the background color const computedStyle = getComputedStyle(this); const bgColor = computedStyle.getPropertyValue('--vscode-editor-background') || '#1e1e1e'; // Parse the color and check brightness const rgb = this.parseColor(bgColor); if (rgb) { // Calculate relative luminance const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255; return luminance < 0.5; } // Default to dark theme return true; } private parseColor( color: string, ): { r: number; g: number; b: number } | null { // Handle hex colors const hexMatch = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i); if (hexMatch) { return { r: parseInt(hexMatch[1], 16), g: parseInt(hexMatch[2], 16), b: parseInt(hexMatch[3], 16), }; } // Handle rgb colors const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (rgbMatch) { return { r: parseInt(rgbMatch[1]), g: parseInt(rgbMatch[2]), b: parseInt(rgbMatch[3]), }; } return null; } private getDarkTheme() { const computedStyle = getComputedStyle(this); return { background: computedStyle.getPropertyValue('--vscode-panel-background') || '#1e1e1e', foreground: computedStyle.getPropertyValue('--vscode-terminal-foreground') || '#cccccc', cursor: computedStyle.getPropertyValue('--vscode-terminalCursor-foreground') || '#cccccc', selectionBackground: computedStyle.getPropertyValue( '--vscode-terminal-selectionBackground', ) || '#264f78', black: computedStyle.getPropertyValue('--vscode-terminal-ansiBlack') || '#000000', red: computedStyle.getPropertyValue('--vscode-terminal-ansiRed') || '#cd3131', green: computedStyle.getPropertyValue('--vscode-terminal-ansiGreen') || '#0dbc79', yellow: computedStyle.getPropertyValue('--vscode-terminal-ansiYellow') || '#e5e510', blue: computedStyle.getPropertyValue('--vscode-terminal-ansiBlue') || '#2472c8', magenta: computedStyle.getPropertyValue('--vscode-terminal-ansiMagenta') || '#bc3fbc', cyan: computedStyle.getPropertyValue('--vscode-terminal-ansiCyan') || '#11a8cd', white: computedStyle.getPropertyValue('--vscode-terminal-ansiWhite') || '#e5e5e5', brightBlack: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightBlack') || '#666666', brightRed: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightRed') || '#f14c4c', brightGreen: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightGreen') || '#23d18b', brightYellow: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightYellow') || '#f5f543', brightBlue: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightBlue') || '#3b8eea', brightMagenta: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightMagenta') || '#d670d6', brightCyan: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightCyan') || '#29b8db', brightWhite: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightWhite') || '#e5e5e5', }; } private getLightTheme() { const computedStyle = getComputedStyle(this); return { background: computedStyle.getPropertyValue('--vscode-panel-background') || '#ffffff', foreground: computedStyle.getPropertyValue('--vscode-terminal-foreground') || '#333333', cursor: computedStyle.getPropertyValue('--vscode-terminalCursor-foreground') || '#333333', selectionBackground: computedStyle.getPropertyValue( '--vscode-terminal-selectionBackground', ) || '#add6ff', black: computedStyle.getPropertyValue('--vscode-terminal-ansiBlack') || '#000000', red: computedStyle.getPropertyValue('--vscode-terminal-ansiRed') || '#cd3131', green: computedStyle.getPropertyValue('--vscode-terminal-ansiGreen') || '#00bc00', yellow: computedStyle.getPropertyValue('--vscode-terminal-ansiYellow') || '#949800', blue: computedStyle.getPropertyValue('--vscode-terminal-ansiBlue') || '#0451a5', magenta: computedStyle.getPropertyValue('--vscode-terminal-ansiMagenta') || '#bc05bc', cyan: computedStyle.getPropertyValue('--vscode-terminal-ansiCyan') || '#0598bc', white: computedStyle.getPropertyValue('--vscode-terminal-ansiWhite') || '#555555', brightBlack: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightBlack') || '#666666', brightRed: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightRed') || '#cd3131', brightGreen: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightGreen') || '#14ce14', brightYellow: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightYellow') || '#b5ba00', brightBlue: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightBlue') || '#0451a5', brightMagenta: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightMagenta') || '#bc05bc', brightCyan: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightCyan') || '#0598bc', brightWhite: computedStyle.getPropertyValue('--vscode-terminal-ansiBrightWhite') || '#a5a5a5', }; } private setupThemeObserver(): void { // Watch for changes to the body's class attribute (VS Code adds theme classes) this.themeObserver = new MutationObserver(() => { this.updateTerminalTheme(); }); // Observe document body for class changes if (document.body) { this.themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-vscode-theme-kind'], }); } // Also listen for style changes on the host element const hostObserver = new MutationObserver(() => { this.updateTerminalTheme(); }); hostObserver.observe(this, { attributes: true, attributeFilter: ['style'], }); } private updateTerminalTheme(): void { if (!this.terminal) return; const isDarkTheme = this.isDarkTheme(); const theme = isDarkTheme ? this.getDarkTheme() : this.getLightTheme(); // Update terminal theme this.terminal.options.theme = theme; // Force a refresh this.terminal.refresh(0, this.terminal.rows - 1); } private setupResizeObserver(): void { this.resizeObserver = new ResizeObserver(() => { if (this.terminal) { this.fitTerminal(); } }); this.resizeObserver.observe(this); } private fitTerminal(): void { if (!this.fitAddon) return; this.fitAddon.fit(); } private updateContent(): void { if (!this.terminal || !this.content) return; this.terminal.clear(); this.terminal.write(this.content); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nrwl/nx-console'

If you have feedback or need assistance with the MCP directory API, please join our Discord server