Inquirer.js icon indicating copy to clipboard operation
Inquirer.js copied to clipboard

bad situation in type: ckeckbox when message is Chinese.

Open gk-shi opened this issue 5 years ago • 9 comments

os: macOS node: v10.18.0 inquirer: "^7.2.0"

When I use inquirer with type:'checkbox', in the property of message,i use Chinese. Actually, when i press down the arrow ,the message will repeatedly show in the console.

in other type , I do not find this situation, also do not find when i use message in English.

so, I want to know wil you fix this ?

the codes is mine.

const inquirer = require('inquirer')


inquirer.prompt([
  {
    type: 'list',
    name: 'cssPreprocessor',
    message: '请选择需要的 CSS 预处理器?(回车确认)',
    choices: [
      { name: '默认 css', value: 'css' },
      { name: 'scss', value: 'scss' },
      { name: 'less', value: 'less' }
    ],
    default: 'css'
  },
  {
    // 集成功能模块
    type: 'checkbox',
    name: 'ciModule',
    message: '请选择模块:',
    choices: [
      { name: '选项1', value: 'value1', checked: true },
      { name: '选项2', value: 'value2', checked: true },
      { name: '选项3', value: 'value3' },
      { name: '选项4', value: 'value4', disabled: true }
    ]
  }])
.then(res => {
  console.log('res ==  ', res)
})

first question is ok ,but second will appear the situation what i showed in the console.


 ~/De/test-in ❯ node test.js                                ✘ INT  16:09:13
? 请选择需要的 CSS 预处理器?(回车确认) less
? 请选择模块: (Press <space> to select, <a> to toggle all, <i> to invert select
? 请选择模块: (Press <space> to select, <a> to toggle all, <i> to invert select
? 请选择模块: (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ◉ 选项1
 ◉ 选项2
❯◯ 选项3
 - 选项4 (Disabled)

gk-shi avatar Jun 26 '20 07:06 gk-shi

Hey @gk-shi, are you sure this is caused by chinese characters? Did you try with only latin characters?

This issue of duplicated top line is something that comes up pretty often - and so far it was usually due to the OS/terminal being used (each aren't 100% normalize, and different Node version had different bugs with readline.)

SBoudrias avatar Jul 09 '20 06:07 SBoudrias

@SBoudrias yeah, In the same environment configuration,using type:checkbox,when i try to use latin characters , the duplicated top line won't appear. my code:

const inquirer = require('inquirer')



inquirer.prompt([
  {
    type: 'list',
    name: 'cssPreprocessor',
    message: '请选择需要的 CSS 预处理器?(回车确认)',
    choices: [
      { name: '默认 css', value: 'css' },
      { name: 'scss', value: 'scss' },
      { name: 'less', value: 'less' }
    ],
    default: 'css'
  },
  {
    // 集成功能模块
    type: 'checkbox',
    name: 'ciModule',
    message: 'choose what you want to add to this project.',
    choices: [
      { name: '选项1', value: 'value1', checked: true },
      { name: '选项2', value: 'value2', checked: true },
      { name: '选项3', value: 'value3' },
      { name: '选项4', value: 'value4', disabled: true }
    ]
  }])
.then(res => {
  console.log('res ==  ', res)
})

I just replace 请选择模块: to choose what you want to add to this project.,this situation disappeared.

As long as there are Chinese characters , it will appear. like choose what you want to add to this project。 or choose what you want to add to this 。 project😹

It's so strange! 😂

gk-shi avatar Jul 09 '20 07:07 gk-shi

Hey @gk-shi, are you sure this is caused by chinese characters? Did you try with only latin characters?

This issue of duplicated top line is something that comes up pretty often - and so far it was usually due to the OS/terminal being used (each aren't 100% normalize, and different Node version had different bugs with readline.)

same problem. Only English characters,top line work good. And I want to konw which version of Node is recommend?I use current LTS Version:12.18.2 and newer version:13.13.0,all have tha same problem.

And I use terminal in bash is also have tha same problem. => This way is from early issue,but now is not work.

XTShow avatar Jul 13 '20 02:07 XTShow

Anyone of you would like to dig in the code and try to figure out where it's coming from?

SBoudrias avatar Jul 13 '20 17:07 SBoudrias

Anyone of you would like to dig in the code and try to figure out where it's coming from?

At present, it seems that there are not many people affected by this bug. So I want to try it.😁 Whether the result is satisfactory or not, I will give a feedback in a week.

gk-shi avatar Jul 14 '20 02:07 gk-shi

I think I found the reason for this bug.

I think this bug is caused by two reasons:

  • The width value of the terminal (or column), when the terminal is enlarged enough to display completely in one row, there will be no such bug
  • A Chinese character takes up twice as much storage space as an English character

This means that it is not only type: 'check box' , but also the problem of using Chinese as message (narrowing the width of the terminal).

When the string is output in the terminal, the function named breaklines in packages/core/Lib/utils.js will be called:

/**
 * Force line returns at specific width. This function is ANSI code friendly and it'll
 * ignore invisible codes during width calculation.
 * @param {string} lines
 * @param {number} width
 * @return {string}
 */
exports.breakLines = (content, width) => {
  const regex = new RegExp('(?:(?:\\033[[0-9;]*m)*.?){1,' + width + '}', 'g');
  return _.flatten(
    content.split('\n').map((line) => {
      const chunk = line.match(regex);
      // Remove the last match as it's always empty
      chunk.pop();
      return chunk || '';
    })
  ).join('\n');
};

However, the width is the width of the terminal. In this case, the regExp matching process will treat the Chinese character as an English character (in fact, it should account for 2 English characters).

eg:


// width = 20

const str1 = 'test'
const str2 = '这是测试'

str1.length // 4
Str2.length  // it's also 4(But when it output to the terminal, it takes up 8 columns)

Therefore, when calling the above functions, the calculated lines that should be displayed at the terminal may not be accurate (in the case of containing similar Chinese characters).

Two solutions came to my mind:

  • In the breakLines function, do special cutting for similar Chinese characters
  • When rerendering is triggered (such as pressing the down arrow key of the keyboard), the total number of rows between the last line of the current terminal and the line with identifier closest to it (the default is?) is obtained, and then the total number is cleared.

For the first solution, I tried to rewrite the breaklines function:

exports.breakLinesIncludeChinese = (content, width) => {
  const contentArr = content.split('\n')
  const result = []
  // regExp for Chinese characters
  const chineseRegex = new RegExp('[\u2E80-\u2EFF\u2F00-\u2FDF\u3000-\u303F\u31C0-\u31EF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FBF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]+', 'g')
  const otherRegex = new RegExp('(?:(?:\\033[[0-9;]*m)*.?){1,' + width + '}', 'g')
  contentArr.forEach(item => {
    // If there is no matching to Chinese, the original matching rules can be used
    if (!chineseRegex.test(item)) {
      result.push(...(item.match(otherRegex).slice(0, -1)))
      return
    }
    // Otherwise, special Chinese processing will be carried out
    result.push(...splitChinese(item, width, chineseRegex))
  })
  return result.join('\n')
}

/**
 * @param {string} str String containing Chinese characters
 * @param {number} width The number of columns in the terminal
 * @param {regexp} chineseRegex regExp for Chinese characters
 */
function splitChinese(str, width, chineseRegex) {
  let len = 0, strArr = [''], index = 0
  // If the length of the string with Chinese characters is smaller than width / 2, it will not exceed one line of terminal display because Chinese characters occupy 2 columns
  if (str.length < width / 2) return [str]

  for (let idx = 0; idx < str.length; idx++) {
    strArr[index] += str[idx]
    // If the current character is Chinese, then + 2
    len += chineseRegex.test(str[idx]) ? 2 : 1

    // If a part of the current string is enough to render a line, need to add \n to start cutting the contents of the next line
    if (len === width || ((len === width - 1) && chineseRegex.test(str[idx + 1]))) {
      index++
      strArr[index] = ''
      len = 0
    }
  }
  
  return strArr
}

But I found that because there are some characters used for coloring in it(like \u001b[32m), it is still inaccurate to cut it out... So, it seems that I have encountered a problem in this way.☹️

As for the second solution, I hope that the packages/core/lib/screen-manager.js When calculating the height that needs to be cleared, we can get the content of the line where the current cursor is located, and match whether the first character is our defined problem identifier (the default is?). If not, move the cursor up one line, and then match until the match is successful, so as to determine the number of lines that should be erased. (at present, the calculation of height is also due to breakLines, which may not be accurate.)

But I haven't found a way to help me get the contents of the cursor line!!! (I have too little knowledge about this...😹)

Because my workload suddenly increased a lot (I believe people who know the working environment of Chinese programmers will understand), I may not be able to spend a lot of time to solve this problem continuously.

Send out the reasons that I have identified, and hope that people who have seen it can provide new ideas. Of course, I still try to solve it when I'm free.😋

gk-shi avatar Jul 15 '20 09:07 gk-shi

Maybe I can offer a little bit more.

When clearing the contents of the terminal by the function height in packages/core/lib/screen-manager.js , I can judge whether it contains Chinese here. If so, I can calculate how many lines the string will actually be displayed in the terminal, and then add up the number of lines that have not been calculated and return them together.


const height = (content, width) => {
  let add = 0
  const reg = new RegExp('(?:(?:\\033[[0-9;]*m)*.?){1,' + width + '}', 'g')
  const contents = content.split('\n')
  contents.forEach(item => {
    // clear these like \u001b[30m strings
    const raw = item.replace(/\\u[\d\w]+\[[0-9;]*m/g, '')
    const chineseRegex = new RegExp('[\u2E80-\u2EFF\u2F00-\u2FDF\u3000-\u303F\u31C0-\u31EF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FBF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]+', 'g')
    const chinese = raw.match(chineseRegex)

    if (chinese) {
      // Each Chinese character is subtracted by one English character length 
      const realLen = chinese.join('').length + raw.length
      add += Math.ceil(realLen / width) - 1
    }
    
  })
  return content.split('\n').length + add;
}

It seems to have solved the original problem, but I found that:

  • According to the terminal width of the string (breakLines), because the Chinese is automatically cut once by the terminal, the output format is a bit strange
  • If you think about Japanese, Korean and so on...

? this is test for Chinese character 发的是分放松放松方式方式方式 是 test t
est (Press <space
> to select, <a> to toggle all, <i> to invert selection)
❯◯ test test 发送方付费电视 test test test test test test test test test test test
 ◯ yarn
 - jspm (disabled)

Test t is automatically cut by the terminal, and< space is cut by breaklines.

gk-shi avatar Jul 17 '20 06:07 gk-shi

same, input repeated caused by chinese character

jeoy avatar Oct 21 '20 07:10 jeoy

I found a quick hack: manully put a EOL(new line) after chinese characters, then this issue cloud be fixed because of this line https://github.com/SBoudrias/Inquirer.js/blob/a3ddaa0c8e8d415c19d9c521cb0bb002c4348301/packages/inquirer/lib/utils/screen-manager.js#L141 This trick can be applied to only a few Chinese characters(which won't issue line break) + many latin characters:

message = '授权地址为:\nhttps://login.partner.microsoftonline.cn/common/oauth2/v2.0/authorize?client_id=1&scope=1&response_type=code&redirect_uri=1'

Hope it helps 🎉 @jeoy @gk-shi also,ping @Axin2017 in #953

yaonyan avatar Feb 08 '21 13:02 yaonyan