1
1
import { basename } from 'node:path' ;
2
2
import { cwd , stdout } from 'node:process' ;
3
3
import type { WriteStream } from 'node:tty' ;
4
+ import { eastAsianWidth } from 'get-east-asian-width' ;
4
5
import type { ChalkInstance } from 'chalk' ;
5
6
import type { TraceOption } from '../shared/index.js' ;
6
7
import TraceError from './TraceError.js' ;
@@ -35,21 +36,32 @@ export default class PrintError extends TraceError {
35
36
* @param {number } [defaultLength] - The default length to use if the title length is not provided.
36
37
* @returns {string } The opening part of the error stack trace.
37
38
*/
38
- private opening ( defaultLength ?: number ) {
39
- const title = ` ${ this . message } ` ;
40
- const { length } = title ;
41
-
42
- const width = this . calc ( ( defaultLength ?? length ) + 2 ) / 2 ;
43
-
44
- const prefixLength = Math . floor ( width ) ;
45
- const prefixString = this . divide ( prefixLength ) ;
39
+ private opening ( defaultLength ?: number ) : string {
40
+ let title = this . padding ( this . message ) ;
41
+ const length = this . length ( title ) ;
42
+
43
+ const width = this . calc ( ( defaultLength ?? length ) + 2 ) ;
44
+ let halfWidth = Math . floor ( width / 2 ) ;
45
+ const isAlternate = halfWidth <= 0 ;
46
+ if ( isAlternate ) {
47
+ title = this . padding ( 'Error Message' ) ;
48
+ halfWidth = Math . floor ( ( width + length - 15 ) / 2 ) ;
49
+ }
50
+
51
+ const prefixString = this . divide ( halfWidth ) ;
46
52
const prefix = this . highlight ( 'red' , prefixString ) ;
47
53
48
- const suffixLength = Math . ceil ( width ) ;
49
- const suffixString = this . divide ( suffixLength ) ;
54
+ const suffixString = this . divide ( halfWidth ) ;
50
55
const suffix = this . highlight ( 'red' , suffixString ) ;
51
56
52
- return `${ prefix } ${ this . highlight ( 'cyanBright' , title ) } ${ suffix } ]` ;
57
+ let output = `${ prefix } ${ this . highlight ( 'cyanBright' , title ) } ${ suffix } ]` ;
58
+ if ( isAlternate ) {
59
+ output += `\n${ this . message } ` ;
60
+ const divider = this . divide ( ( this . column ( ) ?? 32 ) - 2 ) ;
61
+ output += `\n${ this . highlight ( 'grey' , `[${ divider } ]` ) } ` ;
62
+ }
63
+
64
+ return output ;
53
65
}
54
66
55
67
/**
@@ -58,7 +70,7 @@ export default class PrintError extends TraceError {
58
70
* @param {TraceOption[] } track - The array of trace options.
59
71
* @returns {string } The formatted trace information.
60
72
*/
61
- private print ( track : TraceOption [ ] ) {
73
+ private print ( track : TraceOption [ ] ) : string {
62
74
const root : string = basename ( cwd ( ) ) ;
63
75
const { length } = track ;
64
76
@@ -93,11 +105,15 @@ export default class PrintError extends TraceError {
93
105
* @param {number } [defaultLength] - The default length to use if the title length is not provided.
94
106
* @returns {string } The closing part of the error stack trace.
95
107
*/
96
- private closing ( styles ?: string , defaultLength ?: number ) {
97
- const title = this . padding ( this . name ) ;
98
- const { length } = title ;
108
+ private closing ( styles ?: string , defaultLength ?: number ) : string {
109
+ let title = this . padding ( this . name ) ;
110
+ const length = this . length ( title ) ;
99
111
100
- const width = this . calc ( ( defaultLength ?? length ) + 3 ) ;
112
+ let width = this . calc ( ( defaultLength ?? length ) + 3 ) ;
113
+ if ( width <= 0 ) {
114
+ title = this . padding ( 'Exception' ) ;
115
+ width += length - 9 ;
116
+ }
101
117
102
118
const prefix = this . highlight ( 'red' , this . divide ( width ) ) ;
103
119
const suffix = this . highlight ( 'red' , this . divide ( 1 ) ) ;
@@ -106,18 +122,44 @@ export default class PrintError extends TraceError {
106
122
return `[${ prefix } ${ stylish ( title ) } ${ suffix } ` ;
107
123
}
108
124
125
+ /**
126
+ * Calculates the display length of a string, considering East Asian Width rules.
127
+ * @private
128
+ * @param {string } content - The string content whose display length is to be calculated.
129
+ * @returns {number } The total display length of the string, accounting for wide and narrow characters.
130
+ */
131
+ private length ( content : string ) : number {
132
+ let result = 0 ;
133
+ for ( const char of content ) {
134
+ const codePoint = char . codePointAt ( 0 ) ;
135
+ if ( typeof codePoint !== 'number' ) continue ;
136
+
137
+ result += eastAsianWidth ( codePoint ) ;
138
+ }
139
+
140
+ return result ;
141
+ }
142
+
109
143
/**
110
144
* Calculates the width of the terminal.
111
145
* @private
112
146
* @param {number } length - The length of the content.
113
147
* @param {number } [defaultLength=32] - The default length to use if the terminal width cannot be determined.
114
148
* @returns {number } The calculated width.
115
149
*/
116
- private calc ( length : number , defaultLength = 32 ) {
117
- const columns : number | undefined = ( stdout as ( WriteStream & { fd : 1 } ) | undefined ) ?. columns ;
150
+ private calc ( length : number , defaultLength = 32 ) : number {
151
+ const columns = this . column ( ) ;
152
+
153
+ return ( columns ?? defaultLength ) - length ;
154
+ }
118
155
119
- const clientWidth = ( columns ?? defaultLength ) - length ;
120
- return clientWidth <= 0 ? defaultLength : clientWidth ;
156
+ /**
157
+ * Retrieves the current width of the terminal in columns.
158
+ * @private
159
+ * @returns {number | undefined } The number of columns in the terminal, or `undefined` if the terminal width cannot be determined.
160
+ */
161
+ private column ( ) : number | undefined {
162
+ return ( stdout as ( WriteStream & { fd : 1 } ) | undefined ) ?. columns ;
121
163
}
122
164
123
165
/**
@@ -127,7 +169,7 @@ export default class PrintError extends TraceError {
127
169
* @param {string } content - The content to highlight.
128
170
* @returns {string } The highlighted content.
129
171
*/
130
- private highlight ( color : string , content : string ) {
172
+ private highlight ( color : string , content : string ) : string {
131
173
const stylish : ChalkInstance = this . palette ( color ) ;
132
174
return stylish ( content ) ;
133
175
}
@@ -139,7 +181,7 @@ export default class PrintError extends TraceError {
139
181
* @param {string } [separator='-'] - The character to repeat.
140
182
* @returns {string } The string filled with the separator character.
141
183
*/
142
- private divide ( length : number , separator = '-' ) {
184
+ private divide ( length : number , separator = '-' ) : string {
143
185
const ls : string [ ] = Array . from ( { length } ) . map ( ( ) => separator ) ;
144
186
return ls . join ( '' ) ;
145
187
}
0 commit comments