@@ -150,19 +150,16 @@ export function createTmuRoot(options: TmuRootOptions) {
150150 } else if ( key . escape ) {
151151 coordinator . dispatchUi ( { type : "dismissRenameDialog" } ) ;
152152 } else if ( key . return ) {
153- const title = ui . renameDialog . value . trim ( ) ;
154- if ( ! title ) {
155- coordinator . dispatchUi ( { type : "setRenameDialogError" , error : "Track Title must not be empty" } ) ;
156- } else {
157- try {
158- await coordinator . dispatch ( { type : "renameTrack" , identity : ui . renameDialog . identity , title } ) ;
159- coordinator . dispatchUi ( { type : "dismissRenameDialog" } ) ;
160- } catch ( error ) {
161- coordinator . dispatchUi ( {
162- type : "setRenameDialogError" ,
163- error : error instanceof Error ? error . message : String ( error ) ,
164- } ) ;
165- }
153+ try {
154+ await coordinator . dispatch ( {
155+ type : "renameTrack" , identity : ui . renameDialog . identity , title : ui . renameDialog . value ,
156+ } ) ;
157+ coordinator . dispatchUi ( { type : "dismissRenameDialog" } ) ;
158+ } catch ( error ) {
159+ coordinator . dispatchUi ( {
160+ type : "setRenameDialogError" ,
161+ error : error instanceof Error ? error . message : String ( error ) ,
162+ } ) ;
166163 }
167164 } else {
168165 editRenameDialog ( input , key , coordinator ) ;
@@ -742,8 +739,9 @@ function confirmationModal(confirmation: ConfirmationDescriptor, ui: UiState, no
742739
743740function renameTrackModal ( dialog : NonNullable < UiState [ "renameDialog" ] > , noColor : boolean ) {
744741 const beforeCursor = dialog . value . slice ( 0 , dialog . cursor ) ;
745- const atCursor = dialog . value [ dialog . cursor ] ?? " " ;
746- const afterCursor = dialog . value . slice ( dialog . cursor + ( dialog . cursor < dialog . value . length ? 1 : 0 ) ) ;
742+ const nextCursor = nextGraphemeBoundary ( dialog . value , dialog . cursor ) ;
743+ const atCursor = dialog . value . slice ( dialog . cursor , nextCursor ) || " " ;
744+ const afterCursor = dialog . value . slice ( nextCursor ) ;
747745 return h ( Box , {
748746 flexDirection : "column" , borderStyle : "round" , borderColor : noColor ? undefined : "cyan" ,
749747 paddingX : 2 , alignSelf : "center" , width : "70%" ,
@@ -1049,22 +1047,41 @@ function editRenameDialog(input: string, key: InputKey, coordinator: AppCoordina
10491047 const dialog = coordinator . uiState . renameDialog ;
10501048 if ( ! dialog ) return ;
10511049 let { value, cursor } = dialog ;
1052- if ( key . leftArrow ) cursor = Math . max ( 0 , cursor - 1 ) ;
1053- else if ( key . rightArrow ) cursor = Math . min ( value . length , cursor + 1 ) ;
1050+ if ( key . leftArrow ) cursor = previousGraphemeBoundary ( value , cursor ) ;
1051+ else if ( key . rightArrow ) cursor = nextGraphemeBoundary ( value , cursor ) ;
10541052 else if ( key . home ) cursor = 0 ;
10551053 else if ( key . end ) cursor = value . length ;
10561054 else if ( key . backspace && cursor > 0 ) {
1057- value = value . slice ( 0 , cursor - 1 ) + value . slice ( cursor ) ;
1058- cursor -= 1 ;
1055+ const previousCursor = previousGraphemeBoundary ( value , cursor ) ;
1056+ value = value . slice ( 0 , previousCursor ) + value . slice ( cursor ) ;
1057+ cursor = previousCursor ;
10591058 } else if ( key . delete && cursor < value . length ) {
1060- value = value . slice ( 0 , cursor ) + value . slice ( cursor + 1 ) ;
1059+ value = value . slice ( 0 , cursor ) + value . slice ( nextGraphemeBoundary ( value , cursor ) ) ;
10611060 } else if ( input . length > 0 && ! key . ctrl && ! key . meta ) {
10621061 value = value . slice ( 0 , cursor ) + input + value . slice ( cursor ) ;
10631062 cursor += input . length ;
10641063 } else return ;
10651064 coordinator . dispatchUi ( { type : "editRenameDialog" , value, cursor } ) ;
10661065}
10671066
1067+ const graphemeSegmenter = new Intl . Segmenter ( undefined , { granularity : "grapheme" } ) ;
1068+
1069+ function previousGraphemeBoundary ( value : string , cursor : number ) : number {
1070+ let previous = 0 ;
1071+ for ( const segment of graphemeSegmenter . segment ( value ) ) {
1072+ if ( segment . index >= cursor ) break ;
1073+ previous = segment . index ;
1074+ }
1075+ return previous ;
1076+ }
1077+
1078+ function nextGraphemeBoundary ( value : string , cursor : number ) : number {
1079+ for ( const segment of graphemeSegmenter . segment ( value ) ) {
1080+ if ( segment . index > cursor ) return segment . index ;
1081+ }
1082+ return value . length ;
1083+ }
1084+
10681085function isCtrlC ( input : string , key : InputKey ) : boolean {
10691086 return input === "\x03" || ( key . ctrl === true && input === "c" ) ;
10701087}
0 commit comments