Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bad situation in type: ckeckbox when message is Chinese. #934

Open
gk-shi opened this issue Jun 26, 2020 · 9 comments
Open

bad situation in type: ckeckbox when message is Chinese. #934

gk-shi opened this issue Jun 26, 2020 · 9 comments

Comments

@gk-shi
Copy link

gk-shi commented Jun 26, 2020

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)
@SBoudrias
Copy link
Owner

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.)

@gk-shi gk-shi closed this as completed Jul 9, 2020
@gk-shi
Copy link
Author

gk-shi commented Jul 9, 2020

@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! 😂

@SBoudrias SBoudrias reopened this Jul 10, 2020
@XTShow
Copy link

XTShow commented Jul 13, 2020

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.

@SBoudrias SBoudrias added the bug label Jul 13, 2020
@SBoudrias
Copy link
Owner

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

@gk-shi
Copy link
Author

gk-shi commented Jul 14, 2020

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
Copy link
Author

gk-shi commented Jul 15, 2020

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
Copy link
Author

gk-shi commented Jul 17, 2020

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.

@jeoy
Copy link

jeoy commented Oct 21, 2020

same, input repeated caused by chinese character

@beetcb
Copy link

beetcb commented Feb 8, 2021

I found a quick hack: manully put a EOL(new line) after chinese characters, then this issue cloud be fixed because of this line

return _.flatten(this.breakLines(content.split('\n'), width)).join('\n');

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants