웹 IDE 프로젝트를 하면서 터미널화면을 구현했어야 했다. 간단하게 입력값을 넘겨주고 출력값을 벡엔드에서 받아올거라서 react-terminal로 구현했다. 정말 디자인도 마음에 들었는데..입력한 값을 뽑아낼 수가 없었다..여기저기 찾아봐서 해봤지만 계속 안되었다. 그래서 xTerm 라이브러리로 바꾸었다. react-terminal보다는 사용하기 어려웠지만 그래도 괜찮았다.
먼저 사용하기 위해서 아래코드로 xterm과 xterm-addon-fit을 설치해준다.
xter-addon-fit은 터미널의 크기가 안맞아서 컨테이너 크기에 터미널 크기를 자동으로 맞춰주는 애드온이다.
npm install xterm xterm-addon-fit
설치했으면 아래코드처럼 사용해주고 이 컴포넌트를 쓸 곳에 넣어주기만 하면 된다!
import React, { useEffect, useRef } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import 'xterm/css/xterm.css';
import Input from '../../../recoil/Input/atom';
import Output from '../../../recoil/Output/atom';
const XtermComponent: React.FC = () => {
const terminalRef = useRef<HTMLDivElement>(null);
const xtermRef = useRef<Terminal | null>(null);
const fitAddonRef = useRef<FitAddon | null>(null);
const setInput = useSetRecoilState(Input);
const [output, setOutput] = useRecoilState(Output);
let inputBuffer = ''; // 입력된 문자열을 저장할 버퍼
useEffect(() => {
if (terminalRef.current) {
// 터미널과 애드온 인스턴스 생성
const xterm = new Terminal();
const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon);
// 터미널을 DOM 요소에 연결
xterm.open(terminalRef.current);
fitAddon.fit();
// 기본 메시지 출력
xterm.writeln('Welcome to DPIDE!!');
// 사용자 입력 처리
xterm.onData(data => {
if (data === '\r') {
// 엔터 키 입력 처리
xterm.write('\r\n'); // 줄바꿈만 수행
if (inputBuffer.trim() === 'clear') {
// 입력이 'clear'일 경우 터미널 초기화
xterm.clear(); // 터미널 내용 초기화
// eslint-disable-next-line react-hooks/exhaustive-deps
inputBuffer = '';
} else if (inputBuffer.trim()) {
// 입력이 있을 때만 상태 업데이트
setInput(prevInput => prevInput + inputBuffer + '\n'); // Recoil 상태로 inputBuffer 업데이트
// eslint-disable-next-line react-hooks/exhaustive-deps
inputBuffer = '';
}
} else if (data.charCodeAt(0) === 127) {
// 백스페이스 처리
if (inputBuffer.length > 0) {
inputBuffer = inputBuffer.slice(0, -1);//문자열에서 마지막 부분 제외하고 반환
xterm.write('\b \b'); // 터미널에서 백스페이스 표현
}
} else {
inputBuffer += data; // 입력된 데이터를 버퍼에 추가
xterm.write(data); // 터미널에 입력된 데이터 출력
}
});
// 참조에 인스턴스 저장
xtermRef.current = xterm;
fitAddonRef.current = fitAddon;
return () => {
// 컴포넌트 언마운트 시 인스턴스 정리
xterm.dispose();
};
}
}, [setInput]);
// output이 있으면 터미널에 출력하고 상태 초기화
useEffect(() => {
if (output && xtermRef.current) {
const formattedOutput = output.replace(/\n/g, '\r\n');
xtermRef.current.write(formattedOutput);
setOutput(null); // 출력 후 상태 초기화
setInput('');
}
}, [output, setInput, setOutput]);
return <div ref={terminalRef} style={{ width: '100%', height: '100%' }} />;
};
export default XtermComponent;
clear 기능도 넣어주었고 백엔드에 입력값을 넘겨줄 때 엔터를 기준으로 입력값의 개수를 구분하기로 하였는데 xterm에서는 엔터를 따로 인식하지 못하였다. 그래서 setInput(prevInput => prevInput + inputBuffer + '\n'); 이렇게 해주었다.
xTerms는 내가 직접 하나씩 다 구현해줘야했다. 백스페이스 처리도 문자열에서 마지막 부분 제외하고 반환한 다음 터미널에서도 그렇게 보이기 위해서 xterm.write('\b \b')를 해주어야했다. \b로 커서를 한 칸 왼쪽으로 이동시키고 그다음 문자열을 ' '로 공백으로 출력된 문자를 덮어씌우고 다시 \b로 커서를 왼쪽으로 한 칸 이동시킨다.
그리고 실행시켰을 때 백엔드에서 값을 잘 받아왔는데...!!!! 출력할 때 그 다음줄로 넘어가면
hello
sum=8
이런식으로 줄마다 앞에 공백이 생기면서 출력이 되었다.
const formattedOutput = output.replace(/\n/g, '\r\n');
그래서 위에 코드처럼 \n을 \r\n으로 바꿔서 커서를 맨 앞으로 이동시키고 커서가 한 칸 내려가도록 해놨다. 그래서 출력결과도 잘나왔다..!!!!

이번에 xterm에 대해서 많이 알게되었다. 다음에 또 터미널 화면 구현하는 일이 있으면 써야겠담