1+ <template >
2+ <div ref =" terminalRef" class =" xterm-container" ></div >
3+ </template >
4+
5+ <script setup lang="ts">
6+ import { ref , onMounted , onUnmounted , nextTick , watch } from ' vue' ;
7+ import { Terminal } from ' @xterm/xterm' ;
8+ import { FitAddon } from ' @xterm/addon-fit' ;
9+ import ' @xterm/xterm/css/xterm.css' ;
10+
11+ // Props
12+ interface Props {
13+ rows? : number ;
14+ cols? : number ;
15+ fontSize? : number ;
16+ fontFamily? : string ;
17+ theme? : any ;
18+ scrollback? : number ;
19+ cursorBlink? : boolean ;
20+ convertEol? : boolean ;
21+ disableStdin? : boolean ;
22+ }
23+
24+ const props = withDefaults (defineProps <Props >(), {
25+ rows: 20 ,
26+ cols: 80 ,
27+ fontSize: 13 ,
28+ fontFamily: " 'Consolas', 'Monaco', 'Courier New', monospace" ,
29+ theme : () => ({
30+ background: ' #1e1e1e' ,
31+ foreground: ' #d4d4d4' ,
32+ cursor: ' #d4d4d4' ,
33+ black: ' #000000' ,
34+ red: ' #ff6b6b' ,
35+ green: ' #51cf66' ,
36+ yellow: ' #ffd93d' ,
37+ blue: ' #339af0' ,
38+ magenta: ' #ae3ec9' ,
39+ cyan: ' #22b8cf' ,
40+ white: ' #d4d4d4' ,
41+ brightBlack: ' #868e96' ,
42+ brightRed: ' #ff8787' ,
43+ brightGreen: ' #8ce99a' ,
44+ brightYellow: ' #ffe066' ,
45+ brightBlue: ' #74c0fc' ,
46+ brightMagenta: ' #d0bfff' ,
47+ brightCyan: ' #66d9e8' ,
48+ brightWhite: ' #ffffff'
49+ }),
50+ scrollback: 5000 ,
51+ cursorBlink: false ,
52+ convertEol: true ,
53+ disableStdin: true
54+ });
55+
56+ // Emits
57+ const emit = defineEmits <{
58+ ready: [terminal : Terminal ];
59+ data: [data : string ];
60+ }>();
61+
62+ // Refs
63+ const terminalRef = ref <HTMLElement >();
64+ let terminal: Terminal | null = null ;
65+ let fitAddon: FitAddon | null = null ;
66+
67+ // Initialize terminal
68+ const initTerminal = () => {
69+ if (! terminalRef .value || terminal ) return ;
70+
71+ // Create terminal instance
72+ terminal = new Terminal ({
73+ rows: props .rows ,
74+ cols: props .cols ,
75+ fontSize: props .fontSize ,
76+ fontFamily: props .fontFamily ,
77+ theme: props .theme ,
78+ scrollback: props .scrollback ,
79+ cursorBlink: props .cursorBlink ,
80+ convertEol: props .convertEol ,
81+ disableStdin: props .disableStdin
82+ });
83+
84+ // Create fit addon
85+ fitAddon = new FitAddon ();
86+ terminal .loadAddon (fitAddon );
87+
88+ // Mount to DOM
89+ terminal .open (terminalRef .value );
90+
91+ // Listen for terminal input (if not disabled)
92+ if (! props .disableStdin ) {
93+ terminal .onData ((data ) => {
94+ emit (' data' , data );
95+ });
96+ }
97+
98+ // Fit to container
99+ nextTick (() => {
100+ fit ();
101+ // Emit ready event
102+ if (terminal ) {
103+ emit (' ready' , terminal );
104+ }
105+ });
106+ };
107+
108+ // Fit terminal to container
109+ const fit = () => {
110+ if (fitAddon ) {
111+ fitAddon .fit ();
112+ }
113+ };
114+
115+ // Write to terminal
116+ const write = (text : string ) => {
117+ if (terminal ) {
118+ terminal .write (text );
119+ }
120+ };
121+
122+ // Write line to terminal
123+ const writeln = (text : string ) => {
124+ if (terminal ) {
125+ terminal .writeln (text );
126+ }
127+ };
128+
129+ // Clear terminal
130+ const clear = () => {
131+ if (terminal ) {
132+ terminal .clear ();
133+ }
134+ };
135+
136+ // Reset terminal
137+ const reset = () => {
138+ if (terminal ) {
139+ terminal .reset ();
140+ }
141+ };
142+
143+ // Scroll to bottom
144+ const scrollToBottom = () => {
145+ if (terminal ) {
146+ terminal .scrollToBottom ();
147+ }
148+ };
149+
150+ // Scroll to top
151+ const scrollToTop = () => {
152+ if (terminal ) {
153+ terminal .scrollToTop ();
154+ }
155+ };
156+
157+ // Get terminal instance
158+ const getTerminal = () => terminal ;
159+
160+ // Expose methods
161+ defineExpose ({
162+ write ,
163+ writeln ,
164+ clear ,
165+ reset ,
166+ fit ,
167+ scrollToBottom ,
168+ scrollToTop ,
169+ getTerminal
170+ });
171+
172+ // Lifecycle
173+ onMounted (() => {
174+ initTerminal ();
175+
176+ // Listen for window resize
177+ const resizeObserver = new ResizeObserver (() => {
178+ fit ();
179+ });
180+
181+ if (terminalRef .value ) {
182+ resizeObserver .observe (terminalRef .value );
183+ }
184+
185+ // Store observer for cleanup
186+ (window as any ).__xtermResizeObserver = resizeObserver ;
187+ });
188+
189+ onUnmounted (() => {
190+ // Clean up resize observer
191+ const resizeObserver = (window as any ).__xtermResizeObserver ;
192+ if (resizeObserver ) {
193+ resizeObserver .disconnect ();
194+ delete (window as any ).__xtermResizeObserver ;
195+ }
196+
197+ // Clean up terminal
198+ if (terminal ) {
199+ terminal .dispose ();
200+ terminal = null ;
201+ }
202+
203+ if (fitAddon ) {
204+ fitAddon .dispose ();
205+ fitAddon = null ;
206+ }
207+ });
208+ </script >
209+
210+ <style scoped>
211+ .xterm-container {
212+ width : 100% ;
213+ height : 100% ;
214+ min-height : 200px ;
215+ }
216+
217+ :deep(.xterm ) {
218+ padding : 10px ;
219+ }
220+
221+ :deep(.xterm-viewport ) {
222+ background-color : transparent !important ;
223+ }
224+
225+ :deep(.xterm-screen ) {
226+ height : 100% !important ;
227+ }
228+ </style >
0 commit comments