zhangnaisong
2023-08-05 24d66c8d82b628a06e93dbb1abfea2049b3d45ab
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
'use strict';
const sliceAnsi = require('slice-ansi');
const stringWidth = require('string-width');
 
function getIndexOfNearestSpace(string, index, shouldSearchRight) {
    if (string.charAt(index) === ' ') {
        return index;
    }
 
    for (let i = 1; i <= 3; i++) {
        if (shouldSearchRight) {
            if (string.charAt(index + i) === ' ') {
                return index + i;
            }
        } else if (string.charAt(index - i) === ' ') {
            return index - i;
        }
    }
 
    return index;
}
 
module.exports = (text, columns, options) => {
    options = {
        position: 'end',
        preferTruncationOnSpace: false,
        ...options
    };
 
    const {position, space, preferTruncationOnSpace} = options;
    let ellipsis = '…';
    let ellipsisWidth = 1;
 
    if (typeof text !== 'string') {
        throw new TypeError(`Expected \`input\` to be a string, got ${typeof text}`);
    }
 
    if (typeof columns !== 'number') {
        throw new TypeError(`Expected \`columns\` to be a number, got ${typeof columns}`);
    }
 
    if (columns < 1) {
        return '';
    }
 
    if (columns === 1) {
        return ellipsis;
    }
 
    const length = stringWidth(text);
 
    if (length <= columns) {
        return text;
    }
 
    if (position === 'start') {
        if (preferTruncationOnSpace) {
            const nearestSpace = getIndexOfNearestSpace(text, length - columns + 1, true);
            return ellipsis + sliceAnsi(text, nearestSpace, length).trim();
        }
 
        if (space === true) {
            ellipsis += ' ';
            ellipsisWidth = 2;
        }
 
        return ellipsis + sliceAnsi(text, length - columns + ellipsisWidth, length);
    }
 
    if (position === 'middle') {
        if (space === true) {
            ellipsis = ' ' + ellipsis + ' ';
            ellipsisWidth = 3;
        }
 
        const half = Math.floor(columns / 2);
 
        if (preferTruncationOnSpace) {
            const spaceNearFirstBreakPoint = getIndexOfNearestSpace(text, half);
            const spaceNearSecondBreakPoint = getIndexOfNearestSpace(text, length - (columns - half) + 1, true);
            return sliceAnsi(text, 0, spaceNearFirstBreakPoint) + ellipsis + sliceAnsi(text, spaceNearSecondBreakPoint, length).trim();
        }
 
        return (
            sliceAnsi(text, 0, half) +
            ellipsis +
            sliceAnsi(text, length - (columns - half) + ellipsisWidth, length)
        );
    }
 
    if (position === 'end') {
        if (preferTruncationOnSpace) {
            const nearestSpace = getIndexOfNearestSpace(text, columns - 1);
            return sliceAnsi(text, 0, nearestSpace) + ellipsis;
        }
 
        if (space === true) {
            ellipsis = ' ' + ellipsis;
            ellipsisWidth = 2;
        }
 
        return sliceAnsi(text, 0, columns - ellipsisWidth) + ellipsis;
    }
 
    throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${position}`);
};