70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
/**
|
|
* Copyright (c) Microsoft Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import * as getEastAsianWidth from 'get-east-asian-width';
|
|
import { ansiRegex, stripAnsiEscapes } from '@isomorphic/stringUtils';
|
|
|
|
function characterWidth(c: string) {
|
|
return getEastAsianWidth.eastAsianWidth(c.codePointAt(0)!);
|
|
}
|
|
|
|
export function stringWidth(v: string) {
|
|
let width = 0;
|
|
for (const { segment } of new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(v))
|
|
width += characterWidth(segment);
|
|
return width;
|
|
}
|
|
|
|
function suffixOfWidth(v: string, width: number) {
|
|
const segments = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(v)];
|
|
let suffixBegin = v.length;
|
|
for (const { segment, index } of segments.reverse()) {
|
|
const segmentWidth = stringWidth(segment);
|
|
if (segmentWidth > width)
|
|
break;
|
|
width -= segmentWidth;
|
|
suffixBegin = index;
|
|
}
|
|
return v.substring(suffixBegin);
|
|
}
|
|
|
|
export function fitToWidth(line: string, width: number, prefix?: string): string {
|
|
const prefixLength = prefix ? stripAnsiEscapes(prefix).length : 0;
|
|
width -= prefixLength;
|
|
if (stringWidth(line) <= width)
|
|
return line;
|
|
|
|
// Even items are plain text, odd items are control sequences.
|
|
const parts = line.split(ansiRegex);
|
|
const taken: string[] = [];
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
if (i % 2) {
|
|
// Include all control sequences to preserve formatting.
|
|
taken.push(parts[i]);
|
|
} else {
|
|
let part = suffixOfWidth(parts[i], width);
|
|
const wasTruncated = part.length < parts[i].length;
|
|
if (wasTruncated && parts[i].length > 0) {
|
|
// Add ellipsis if we are truncating.
|
|
part = '\u2026' + suffixOfWidth(parts[i], width - 1);
|
|
}
|
|
taken.push(part);
|
|
width -= stringWidth(part);
|
|
}
|
|
}
|
|
return taken.reverse().join('');
|
|
}
|