如何让 Xterm.js 正确调整大小?

AWo*_*olf 5 javascript css reactjs xtermjs

tl;dr 我创建了一个 React 包装器来将日志消息数组渲染到终端中,但调整大小会产生奇怪的输出(参见屏幕截图)。(NPM 上有一个 React-Wrapper,但它不适用于我的用例 - 导致屏幕闪烁)

我正在为 Guppy 开发一项功能,其中我为终端输出添加Xterm.js 。该 PR 可以在这里找到。

由于超链接扫描/解析,我添加了 xterm,并且它正在工作。

但我一直坚持调整大小才能工作。如果我在应用程序中启动 devServer 并等待一些文本,它将以正确的字母宽度显示。如果减小尺寸,我会得到字母宽度不正确的输出。就像下面的截图一样: 输出混乱

在未调整大小的状态下它看起来总是正确的,但调整大小后它会得到错误的显示 - 因此放大和缩小屏幕宽度时会发生这种情况。

输出应类似于以下屏幕截图(可能带有一些换行): 正确输出

我认为这是由Fit 插件或我使用调整大小观察器处理调整大小的方式引起的,但我不确定。

xterm 字母的跨度样式的宽度NaNpx如下图所示: 宽度为 NaNpx 的 CSS 这是由我正在使用的媒体查询引起的吗?我还没有看到这一点,也许我必须暂时禁用所有媒体查询,看看这是否导致了这种行为。

到目前为止我已经尝试过:

  • 包裹this.xterm.fit()成一个setTimeout(func, 0)但没有效果
  • 修改了我正在使用的一些样式,但我还没有找到原因。

代码

我使用的代码可以在Github 分支 feature-terminal-links上找到,但在这里我想提取我添加的部分以使 Xterm 与 React 一起使用:

  1. 我创建了一个样式组件XtermContainer作为 div,这样我就可以添加 Xterm 样式和自己的样式。以下代码位于内部render,并将成为我们的 xterm.js 容器(innerRef稍后将使用ComponentDidMount该容器初始化 Xterm):
<XtermContainer
    width={width}
    height={height}
    innerRef={node => (this.node = node)}
/>
Run Code Online (Sandbox Code Playgroud)
  1. componentDidMount使用上面的容器初始化 xterm :
componentDidMount() {
    Terminal.applyAddon(webLinks);
    Terminal.applyAddon(localLinks);
    Terminal.applyAddon(fit);

    this.xterm = new Terminal({
      convertEol: true,
      fontFamily: `'Fira Mono', monospace`,
      fontSize: 15,
      rendererType: 'dom', // default is canvas
    });

    this.xterm.setOption('theme', {
      background: COLORS.blue[900],
      foreground: COLORS.white,
    });

    this.xterm.open(this.node);
    this.xterm.fit();

    /* ... some addon setup code here (not relevant for the problem) ... */
}
Run Code Online (Sandbox Code Playgroud)
  1. 在包装器内部添加了react-resize-observer,this.xterm.fit()该包装器也包含终端容器,这样我就可以在大小发生变化时触发(在存储库中有一个setTimeout用于测试的包装器)。
<ResizeObserver onResize={() => this.xterm && this.xterm.fit()} />
Run Code Online (Sandbox Code Playgroud)
  1. 如果组件正在获取新日志,则用于componentDidUpdate(prevProps, prevState)更新终端并将终端滚动到底部:
componentDidUpdate(prevProps, prevState) {
    if (prevProps.task.logs !== this.state.logs) {
      if (this.state.logs.length === 0) {
        this.xterm.clear();
      }
      for (const log of this.state.logs) {
        /*
        We need to track what we have added to xterm - feels hacky but it's working.
        `this.xterm.clear()` and re-render everything caused screen flicker that's why I decided to not use it.
        Todo: Check if there is a react-xterm wrapper that is not using xterm.clear or 
              create a wrapper component that can render the logs array (with-out flicker).
        */
        if (!this.renderedLogs[log.id]) {
          this.writeln(log.text);
          this.xterm.scrollToBottom();
          this.renderedLogs[log.id] = true;
        }
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的想法必须找到原因:

  • 检查 ResizeObserver 代码。(请参阅下面的更新)
  • 尝试找出 xterm css 宽度为 NaN 的原因。Xterm.js 是否使用容器的样式宽度?如果是的话,可能设置不正确。

更新

好的,可能不需要调整大小观察者,因为在注释掉渲染后我得到了相同的行为<ResizeObserver/>。所以我认为这是由 xterm.js 或 Guppy 中的 css 引起的。

AWo*_*olf 4

我有一个解决这个问题的方法。它现在正在上述功能分支中工作。不确定是否有更好的解决方案,但它对我有用。

我想解释一下我是如何解决调整大小问题的:

问题在于OnlyOn中使用的组件DevelopmentServerPane。它总是呈现两个TerminalOutput组件。一个终端被隐藏display: none,另一个终端被显示display: inline- 样式更改是通过样式组件内的媒体查询来处理的。

替换为OnlyOnReact-responsive并使用 render props 检查mdMin断点后,它按预期工作。React-responsive 正在从 DOM 中删除未显示的 mediaquery 组件,因此 DOM 中同时只有一个终端。

我仍然不知道为什么字母宽度有问题,但可能两个实例以某种方式发生了碰撞。我无法创建最小的复制品。我尝试在此重现该问题Codesandbox中重新创建该问题,但我一次只调整了一个终端的大小,因此我没有遇到该问题。

解决问题的代码(上述存储库的简化版本):

import MediaQuery from 'react-responsive';

const BREAKPOINT_SIZES = {
  sm: 900,
};

const BREAKPOINTS = {
  mdMin: `(min-width: ${BREAKPOINT_SIZES.sm + 1}px)`,
};

const DevelopmentServerPane = () => (
  <MediaQuery query={BREAKPOINTS['mdMin']}>
    {matches =>
      matches ? (
        <div>{/* ... render Terminal for matching mdMin and above */}</div>
      ) : (
        <div> {/* ... render Terminal for small screens */}</div>
      )
    }
  </MediaQuery>
);

Run Code Online (Sandbox Code Playgroud)