@@ -316,45 +316,60 @@ contains |
| 316 | type(buffer_t), intent(in) :: buffer | 316 | type(buffer_t), intent(in) :: buffer |
| 317 | type(editor_state_t), intent(in) :: editor | 317 | type(editor_state_t), intent(in) :: editor |
| 318 | integer, intent(in) :: line_num, start_col, width | 318 | integer, intent(in) :: line_num, start_col, width |
| 319 | - character(len=:), allocatable :: line | 319 | + character(len=:), allocatable :: line, utf8_ch |
| 320 | - integer :: i, col, line_len, token_idx | 320 | + integer :: i, char_idx, byte_pos, token_idx, char_count, display_col, char_width |
| 321 | integer :: sel_start_line, sel_start_col, sel_end_line, sel_end_col | 321 | integer :: sel_start_line, sel_start_col, sel_end_line, sel_end_col |
| 322 | logical :: in_selection, is_bracket_match, is_current_line, is_search_match | 322 | logical :: in_selection, is_bracket_match, is_current_line, is_search_match |
| 323 | - character :: ch | | |
| 324 | type(token_t), allocatable :: tokens(:) | 323 | type(token_t), allocatable :: tokens(:) |
| 325 | character(len=:), allocatable :: token_color | 324 | character(len=:), allocatable :: token_color |
| 326 | integer :: search_matches(2, 50) ! Up to 50 matches per line (start, end pairs) | 325 | integer :: search_matches(2, 50) ! Up to 50 matches per line (start, end pairs) |
| 327 | integer :: num_search_matches, match_idx | 326 | integer :: num_search_matches, match_idx |
| | 327 | + integer :: line_byte_len |
| 328 | | 328 | |
| 329 | line = buffer_get_line(buffer, line_num) | 329 | line = buffer_get_line(buffer, line_num) |
| 330 | - line_len = len(line) | 330 | + line_byte_len = len(line) |
| | 331 | + char_count = utf8_char_count(line) |
| 331 | | 332 | |
| 332 | - ! Get all search matches on this line | 333 | + ! Get all search matches on this line (these use byte indices) |
| 333 | if (search_mode_active) then | 334 | if (search_mode_active) then |
| 334 | call get_matches_on_line(line, line_num, search_matches, num_search_matches) | 335 | call get_matches_on_line(line, line_num, search_matches, num_search_matches) |
| 335 | else | 336 | else |
| 336 | num_search_matches = 0 | 337 | num_search_matches = 0 |
| 337 | end if | 338 | end if |
| 338 | | 339 | |
| 339 | - ! Get syntax tokens for this line | 340 | + ! Get syntax tokens for this line (tokens use byte indices) |
| 340 | if (syntax_highlighter%enabled) then | 341 | if (syntax_highlighter%enabled) then |
| 341 | call tokenize_line(syntax_highlighter, line, tokens) | 342 | call tokenize_line(syntax_highlighter, line, tokens) |
| 342 | else | 343 | else |
| 343 | allocate(tokens(1)) | 344 | allocate(tokens(1)) |
| 344 | tokens(1)%type = TOKEN_PLAIN | 345 | tokens(1)%type = TOKEN_PLAIN |
| 345 | tokens(1)%start_col = 1 | 346 | tokens(1)%start_col = 1 |
| 346 | - tokens(1)%end_col = max(1, line_len) | 347 | + tokens(1)%end_col = max(1, line_byte_len) |
| 347 | end if | 348 | end if |
| 348 | | 349 | |
| 349 | ! Check if this is the current line | 350 | ! Check if this is the current line |
| 350 | is_current_line = (line_num == editor%cursors(editor%active_cursor)%line) .and. highlight_current_line | 351 | is_current_line = (line_num == editor%cursors(editor%active_cursor)%line) .and. highlight_current_line |
| 351 | | 352 | |
| 352 | - ! Render each character with selection highlighting | 353 | + ! Render each UTF-8 character with selection highlighting |
| 353 | - do col = start_col, min(start_col + width - 1, line_len + 1) | 354 | + ! char_idx = 1-based character index (for selection logic) |
| | 355 | + ! display_col = screen column position (for width tracking) |
| | 356 | + ! byte_pos = byte position in string (for token lookup) |
| | 357 | + display_col = 0 |
| | 358 | + char_idx = start_col |
| | 359 | + |
| | 360 | + do while (char_idx <= char_count .and. display_col < width) |
| 354 | in_selection = .false. | 361 | in_selection = .false. |
| 355 | is_bracket_match = .false. | 362 | is_bracket_match = .false. |
| 356 | | 363 | |
| | 364 | + ! Get the UTF-8 character at this position |
| | 365 | + utf8_ch = utf8_char_at(line, char_idx) |
| | 366 | + char_width = utf8_display_width(utf8_ch) |
| | 367 | + |
| | 368 | + ! Get byte position for token lookup |
| | 369 | + byte_pos = utf8_char_to_byte_index(line, char_idx) |
| | 370 | + |
| 357 | ! Check if this position is in any cursor's selection | 371 | ! Check if this position is in any cursor's selection |
| | 372 | + ! (cursor positions are character indices, not byte indices) |
| 358 | do i = 1, size(editor%cursors) | 373 | do i = 1, size(editor%cursors) |
| 359 | if (editor%cursors(i)%has_selection) then | 374 | if (editor%cursors(i)%has_selection) then |
| 360 | ! Determine selection bounds (handle both directions) | 375 | ! Determine selection bounds (handle both directions) |
@@ -374,26 +389,26 @@ contains |
| 374 | sel_end_col = editor%cursors(i)%column | 389 | sel_end_col = editor%cursors(i)%column |
| 375 | end if | 390 | end if |
| 376 | | 391 | |
| 377 | - ! Check if this position is selected | 392 | + ! Check if this position is selected (using char_idx) |
| 378 | if (line_num > sel_start_line .and. line_num < sel_end_line) then | 393 | if (line_num > sel_start_line .and. line_num < sel_end_line) then |
| 379 | ! Fully selected line (between start and end) | 394 | ! Fully selected line (between start and end) |
| 380 | in_selection = .true. | 395 | in_selection = .true. |
| 381 | exit | 396 | exit |
| 382 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then | 397 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then |
| 383 | ! Single-line selection | 398 | ! Single-line selection |
| 384 | - if (col >= sel_start_col .and. col < sel_end_col) then | 399 | + if (char_idx >= sel_start_col .and. char_idx < sel_end_col) then |
| 385 | in_selection = .true. | 400 | in_selection = .true. |
| 386 | exit | 401 | exit |
| 387 | end if | 402 | end if |
| 388 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then | 403 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then |
| 389 | ! First line of multi-line selection | 404 | ! First line of multi-line selection |
| 390 | - if (col >= sel_start_col) then | 405 | + if (char_idx >= sel_start_col) then |
| 391 | in_selection = .true. | 406 | in_selection = .true. |
| 392 | exit | 407 | exit |
| 393 | end if | 408 | end if |
| 394 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then | 409 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then |
| 395 | ! Last line of multi-line selection | 410 | ! Last line of multi-line selection |
| 396 | - if (col < sel_end_col) then | 411 | + if (char_idx < sel_end_col) then |
| 397 | in_selection = .true. | 412 | in_selection = .true. |
| 398 | exit | 413 | exit |
| 399 | end if | 414 | end if |
@@ -401,26 +416,28 @@ contains |
| 401 | end if | 416 | end if |
| 402 | end do | 417 | end do |
| 403 | | 418 | |
| 404 | - ! Check if this position is a bracket or its match | 419 | + ! Check if this position is a bracket or its match (using char_idx) |
| 405 | - if ((line_num == bracket_line .and. col == bracket_col) .or. & | 420 | + if ((line_num == bracket_line .and. char_idx == bracket_col) .or. & |
| 406 | - (line_num == matching_bracket_line .and. col == matching_bracket_col)) then | 421 | + (line_num == matching_bracket_line .and. char_idx == matching_bracket_col)) then |
| 407 | is_bracket_match = .true. | 422 | is_bracket_match = .true. |
| 408 | end if | 423 | end if |
| 409 | | 424 | |
| 410 | - ! Check if this position is part of a search match | 425 | + ! Check if this position is part of a search match (search uses byte indices) |
| 411 | is_search_match = .false. | 426 | is_search_match = .false. |
| 412 | - do match_idx = 1, num_search_matches | 427 | + if (byte_pos > 0) then |
| 413 | - if (col >= search_matches(1, match_idx) .and. col <= search_matches(2, match_idx)) then | 428 | + do match_idx = 1, num_search_matches |
| 414 | - is_search_match = .true. | 429 | + if (byte_pos >= search_matches(1, match_idx) .and. byte_pos <= search_matches(2, match_idx)) then |
| 415 | - exit | 430 | + is_search_match = .true. |
| 416 | - end if | 431 | + exit |
| 417 | - end do | 432 | + end if |
| | 433 | + end do |
| | 434 | + end if |
| 418 | | 435 | |
| 419 | - ! Find which token this column belongs to | 436 | + ! Find which token this column belongs to (tokens use byte indices) |
| 420 | token_color = "" | 437 | token_color = "" |
| 421 | - if (syntax_highlighter%enabled) then | 438 | + if (syntax_highlighter%enabled .and. byte_pos > 0) then |
| 422 | do token_idx = 1, size(tokens) | 439 | do token_idx = 1, size(tokens) |
| 423 | - if (col >= tokens(token_idx)%start_col .and. col <= tokens(token_idx)%end_col) then | 440 | + if (byte_pos >= tokens(token_idx)%start_col .and. byte_pos <= tokens(token_idx)%end_col) then |
| 424 | token_color = get_token_color(tokens(token_idx)%type) | 441 | token_color = get_token_color(tokens(token_idx)%type) |
| 425 | exit | 442 | exit |
| 426 | end if | 443 | end if |
@@ -428,42 +445,39 @@ contains |
| 428 | end if | 445 | end if |
| 429 | | 446 | |
| 430 | ! Render character with or without highlighting | 447 | ! Render character with or without highlighting |
| 431 | - if (col <= line_len) then | | |
| 432 | - ch = line(col:col) | | |
| 433 | - else | | |
| 434 | - ch = ' ' | | |
| 435 | - end if | | |
| 436 | - | | |
| 437 | if (in_selection) then | 448 | if (in_selection) then |
| 438 | ! Highlight selected text with reverse video (highest priority) | 449 | ! Highlight selected text with reverse video (highest priority) |
| 439 | - call terminal_write(char(27) // '[7m' // ch // char(27) // '[0m') | 450 | + call terminal_write(char(27) // '[7m' // utf8_ch // char(27) // '[0m') |
| 440 | else if (is_bracket_match) then | 451 | else if (is_bracket_match) then |
| 441 | ! Highlight matching brackets with cyan background | 452 | ! Highlight matching brackets with cyan background |
| 442 | - call terminal_write(char(27) // '[46m' // ch // char(27) // '[0m') | 453 | + call terminal_write(char(27) // '[46m' // utf8_ch // char(27) // '[0m') |
| 443 | else if (is_search_match) then | 454 | else if (is_search_match) then |
| 444 | ! Highlight search matches with yellow background | 455 | ! Highlight search matches with yellow background |
| 445 | if (len(token_color) > 0) then | 456 | if (len(token_color) > 0) then |
| 446 | - call terminal_write(token_color // char(27) // '[43m' // ch // char(27) // '[0m') | 457 | + call terminal_write(token_color // char(27) // '[43m' // utf8_ch // char(27) // '[0m') |
| 447 | else | 458 | else |
| 448 | - call terminal_write(char(27) // '[43m' // ch // char(27) // '[0m') | 459 | + call terminal_write(char(27) // '[43m' // utf8_ch // char(27) // '[0m') |
| 449 | end if | 460 | end if |
| 450 | else if (is_current_line) then | 461 | else if (is_current_line) then |
| 451 | ! Subtle background for current line with syntax color | 462 | ! Subtle background for current line with syntax color |
| 452 | if (len(token_color) > 0) then | 463 | if (len(token_color) > 0) then |
| 453 | - call terminal_write(token_color // char(27) // '[48;5;236m' // ch // char(27) // '[0m') | 464 | + call terminal_write(token_color // char(27) // '[48;5;236m' // utf8_ch // char(27) // '[0m') |
| 454 | else | 465 | else |
| 455 | - call terminal_write(char(27) // '[48;5;236m' // ch // char(27) // '[0m') | 466 | + call terminal_write(char(27) // '[48;5;236m' // utf8_ch // char(27) // '[0m') |
| 456 | end if | 467 | end if |
| 457 | else if (len(token_color) > 0) then | 468 | else if (len(token_color) > 0) then |
| 458 | ! Apply syntax highlighting | 469 | ! Apply syntax highlighting |
| 459 | - call terminal_write(token_color // ch // char(27) // '[0m') | 470 | + call terminal_write(token_color // utf8_ch // char(27) // '[0m') |
| 460 | else | 471 | else |
| 461 | - call terminal_write(ch) | 472 | + call terminal_write(utf8_ch) |
| 462 | end if | 473 | end if |
| | 474 | + |
| | 475 | + display_col = display_col + char_width |
| | 476 | + char_idx = char_idx + 1 |
| 463 | end do | 477 | end do |
| 464 | | 478 | |
| 465 | ! Fill remaining width with spaces | 479 | ! Fill remaining width with spaces |
| 466 | - do col = max(line_len + 1, start_col), start_col + width - 1 | 480 | + do while (display_col < width) |
| 467 | in_selection = .false. | 481 | in_selection = .false. |
| 468 | | 482 | |
| 469 | ! Check if end of line position is in selection | 483 | ! Check if end of line position is in selection |
@@ -485,25 +499,26 @@ contains |
| 485 | end if | 499 | end if |
| 486 | | 500 | |
| 487 | ! Check if this position is selected (multi-line aware) | 501 | ! Check if this position is selected (multi-line aware) |
| | 502 | + ! Use char_idx which is now past end of line content |
| 488 | if (line_num > sel_start_line .and. line_num < sel_end_line) then | 503 | if (line_num > sel_start_line .and. line_num < sel_end_line) then |
| 489 | ! Fully selected line | 504 | ! Fully selected line |
| 490 | in_selection = .true. | 505 | in_selection = .true. |
| 491 | exit | 506 | exit |
| 492 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then | 507 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then |
| 493 | ! Single-line selection | 508 | ! Single-line selection |
| 494 | - if (col >= sel_start_col .and. col < sel_end_col) then | 509 | + if (char_idx >= sel_start_col .and. char_idx < sel_end_col) then |
| 495 | in_selection = .true. | 510 | in_selection = .true. |
| 496 | exit | 511 | exit |
| 497 | end if | 512 | end if |
| 498 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then | 513 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then |
| 499 | ! First line of multi-line selection | 514 | ! First line of multi-line selection |
| 500 | - if (col >= sel_start_col) then | 515 | + if (char_idx >= sel_start_col) then |
| 501 | in_selection = .true. | 516 | in_selection = .true. |
| 502 | exit | 517 | exit |
| 503 | end if | 518 | end if |
| 504 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then | 519 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then |
| 505 | ! Last line of multi-line selection | 520 | ! Last line of multi-line selection |
| 506 | - if (col < sel_end_col) then | 521 | + if (char_idx < sel_end_col) then |
| 507 | in_selection = .true. | 522 | in_selection = .true. |
| 508 | exit | 523 | exit |
| 509 | end if | 524 | end if |
@@ -518,9 +533,12 @@ contains |
| 518 | else | 533 | else |
| 519 | call terminal_write(' ') | 534 | call terminal_write(' ') |
| 520 | end if | 535 | end if |
| | 536 | + display_col = display_col + 1 |
| | 537 | + char_idx = char_idx + 1 |
| 521 | end do | 538 | end do |
| 522 | | 539 | |
| 523 | if (allocated(line)) deallocate(line) | 540 | if (allocated(line)) deallocate(line) |
| | 541 | + if (allocated(utf8_ch)) deallocate(utf8_ch) |
| 524 | end subroutine render_line_with_selections | 542 | end subroutine render_line_with_selections |
| 525 | | 543 | |
| 526 | subroutine render_status_bar(editor, buffer, match_mode_active, match_case_sens) | 544 | subroutine render_status_bar(editor, buffer, match_mode_active, match_case_sens) |
@@ -1319,14 +1337,13 @@ contains |
| 1319 | type(buffer_t), intent(in) :: buffer | 1337 | type(buffer_t), intent(in) :: buffer |
| 1320 | type(editor_state_t), intent(in) :: editor | 1338 | type(editor_state_t), intent(in) :: editor |
| 1321 | integer, intent(in) :: pane_idx, line_num, screen_row, col, width | 1339 | integer, intent(in) :: pane_idx, line_num, screen_row, col, width |
| 1322 | - character(len=:), allocatable :: line | 1340 | + character(len=:), allocatable :: line, utf8_ch |
| 1323 | type(pane_t) :: pane | 1341 | type(pane_t) :: pane |
| 1324 | - integer :: tab_idx, start_col, end_col, i, char_col | 1342 | + integer :: tab_idx, i, char_idx, char_count, display_col, char_width |
| 1325 | integer :: content_width, content_col | 1343 | integer :: content_width, content_col |
| 1326 | character(len=5) :: line_num_str | 1344 | character(len=5) :: line_num_str |
| 1327 | logical :: is_current_line, in_selection, is_bracket_match | 1345 | logical :: is_current_line, in_selection, is_bracket_match |
| 1328 | integer :: sel_start_line, sel_start_col, sel_end_line, sel_end_col | 1346 | integer :: sel_start_line, sel_start_col, sel_end_line, sel_end_col |
| 1329 | - character(len=1) :: ch | | |
| 1330 | | 1347 | |
| 1331 | tab_idx = editor%active_tab_index | 1348 | tab_idx = editor%active_tab_index |
| 1332 | pane = editor%tabs(tab_idx)%panes(pane_idx) | 1349 | pane = editor%tabs(tab_idx)%panes(pane_idx) |
@@ -1377,9 +1394,8 @@ contains |
| 1377 | line = buffer_get_line(buffer, line_num) | 1394 | line = buffer_get_line(buffer, line_num) |
| 1378 | if (.not. allocated(line)) return | 1395 | if (.not. allocated(line)) return |
| 1379 | | 1396 | |
| 1380 | - ! Calculate visible portion based on horizontal scroll | 1397 | + ! Get character count for UTF-8 iteration |
| 1381 | - start_col = pane%viewport_column | 1398 | + char_count = utf8_char_count(line) |
| 1382 | - end_col = min(start_col + content_width - 1, len(line)) | | |
| 1383 | | 1399 | |
| 1384 | ! Check if this is the current line with a cursor | 1400 | ! Check if this is the current line with a cursor |
| 1385 | is_current_line = .false. | 1401 | is_current_line = .false. |
@@ -1393,9 +1409,17 @@ contains |
| 1393 | end if | 1409 | end if |
| 1394 | | 1410 | |
| 1395 | ! Render the line character by character with selection highlighting | 1411 | ! Render the line character by character with selection highlighting |
| 1396 | - do char_col = start_col, start_col + content_width - 1 | 1412 | + ! Using UTF-8 aware iteration |
| | 1413 | + display_col = 0 |
| | 1414 | + char_idx = pane%viewport_column ! Start from viewport column (character index) |
| | 1415 | + |
| | 1416 | + do while (char_idx <= char_count .and. display_col < content_width) |
| 1397 | in_selection = .false. | 1417 | in_selection = .false. |
| 1398 | | 1418 | |
| | 1419 | + ! Get the UTF-8 character at this position |
| | 1420 | + utf8_ch = utf8_char_at(line, char_idx) |
| | 1421 | + char_width = utf8_display_width(utf8_ch) |
| | 1422 | + |
| 1399 | ! Check if this position is in any cursor's selection (use pane's cursors) | 1423 | ! Check if this position is in any cursor's selection (use pane's cursors) |
| 1400 | if (allocated(pane%cursors)) then | 1424 | if (allocated(pane%cursors)) then |
| 1401 | do i = 1, size(pane%cursors) | 1425 | do i = 1, size(pane%cursors) |
@@ -1417,26 +1441,26 @@ contains |
| 1417 | sel_end_col = pane%cursors(i)%column | 1441 | sel_end_col = pane%cursors(i)%column |
| 1418 | end if | 1442 | end if |
| 1419 | | 1443 | |
| 1420 | - ! Check if this position is selected | 1444 | + ! Check if this position is selected (using char_idx) |
| 1421 | if (line_num > sel_start_line .and. line_num < sel_end_line) then | 1445 | if (line_num > sel_start_line .and. line_num < sel_end_line) then |
| 1422 | ! Fully selected line (between start and end) | 1446 | ! Fully selected line (between start and end) |
| 1423 | in_selection = .true. | 1447 | in_selection = .true. |
| 1424 | exit | 1448 | exit |
| 1425 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then | 1449 | else if (line_num == sel_start_line .and. line_num == sel_end_line) then |
| 1426 | ! Single-line selection | 1450 | ! Single-line selection |
| 1427 | - if (char_col >= sel_start_col .and. char_col < sel_end_col) then | 1451 | + if (char_idx >= sel_start_col .and. char_idx < sel_end_col) then |
| 1428 | in_selection = .true. | 1452 | in_selection = .true. |
| 1429 | exit | 1453 | exit |
| 1430 | end if | 1454 | end if |
| 1431 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then | 1455 | else if (line_num == sel_start_line .and. line_num < sel_end_line) then |
| 1432 | ! First line of multi-line selection | 1456 | ! First line of multi-line selection |
| 1433 | - if (char_col >= sel_start_col) then | 1457 | + if (char_idx >= sel_start_col) then |
| 1434 | in_selection = .true. | 1458 | in_selection = .true. |
| 1435 | exit | 1459 | exit |
| 1436 | end if | 1460 | end if |
| 1437 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then | 1461 | else if (line_num == sel_end_line .and. line_num > sel_start_line) then |
| 1438 | ! Last line of multi-line selection | 1462 | ! Last line of multi-line selection |
| 1439 | - if (char_col < sel_end_col) then | 1463 | + if (char_idx < sel_end_col) then |
| 1440 | in_selection = .true. | 1464 | in_selection = .true. |
| 1441 | exit | 1465 | exit |
| 1442 | end if | 1466 | end if |
@@ -1448,40 +1472,50 @@ contains |
| 1448 | ! Check if this position is a bracket or its match (only for active pane) | 1472 | ! Check if this position is a bracket or its match (only for active pane) |
| 1449 | is_bracket_match = .false. | 1473 | is_bracket_match = .false. |
| 1450 | if (pane%is_active) then | 1474 | if (pane%is_active) then |
| 1451 | - if ((line_num == bracket_line .and. char_col == bracket_col) .or. & | 1475 | + if ((line_num == bracket_line .and. char_idx == bracket_col) .or. & |
| 1452 | - (line_num == matching_bracket_line .and. char_col == matching_bracket_col)) then | 1476 | + (line_num == matching_bracket_line .and. char_idx == matching_bracket_col)) then |
| 1453 | is_bracket_match = .true. | 1477 | is_bracket_match = .true. |
| 1454 | end if | 1478 | end if |
| 1455 | end if | 1479 | end if |
| 1456 | | 1480 | |
| 1457 | - ! Get the character at this position | | |
| 1458 | - if (char_col <= len(line)) then | | |
| 1459 | - ch = line(char_col:char_col) | | |
| 1460 | - else | | |
| 1461 | - ch = ' ' | | |
| 1462 | - end if | | |
| 1463 | - | | |
| 1464 | ! Render the character with appropriate highlighting | 1481 | ! Render the character with appropriate highlighting |
| 1465 | if (in_selection) then | 1482 | if (in_selection) then |
| 1466 | ! Highlight selected text with reverse video | 1483 | ! Highlight selected text with reverse video |
| 1467 | - call terminal_write(char(27) // '[7m' // ch // char(27) // '[0m') | 1484 | + call terminal_write(char(27) // '[7m' // utf8_ch // char(27) // '[0m') |
| 1468 | else if (is_bracket_match) then | 1485 | else if (is_bracket_match) then |
| 1469 | ! Highlight matching brackets with cyan background | 1486 | ! Highlight matching brackets with cyan background |
| 1470 | - call terminal_write(char(27) // '[46m' // ch // char(27) // '[0m') | 1487 | + call terminal_write(char(27) // '[46m' // utf8_ch // char(27) // '[0m') |
| 1471 | else if (pane%is_active .and. is_current_line) then | 1488 | else if (pane%is_active .and. is_current_line) then |
| 1472 | ! Subtle background for current line in active pane | 1489 | ! Subtle background for current line in active pane |
| 1473 | - call terminal_write(char(27) // '[48;5;237m' // ch // char(27) // '[0m') | 1490 | + call terminal_write(char(27) // '[48;5;237m' // utf8_ch // char(27) // '[0m') |
| 1474 | else if (.not. pane%is_active) then | 1491 | else if (.not. pane%is_active) then |
| 1475 | ! Inactive pane background | 1492 | ! Inactive pane background |
| 1476 | - call terminal_write(char(27) // '[48;5;234m' // ch // char(27) // '[0m') | 1493 | + call terminal_write(char(27) // '[48;5;234m' // utf8_ch // char(27) // '[0m') |
| 1477 | else | 1494 | else |
| 1478 | ! Normal text | 1495 | ! Normal text |
| 1479 | - call terminal_write(ch) | 1496 | + call terminal_write(utf8_ch) |
| | 1497 | + end if |
| | 1498 | + |
| | 1499 | + display_col = display_col + char_width |
| | 1500 | + char_idx = char_idx + 1 |
| | 1501 | + end do |
| | 1502 | + |
| | 1503 | + ! Fill remaining width with spaces |
| | 1504 | + do while (display_col < content_width) |
| | 1505 | + if (.not. pane%is_active) then |
| | 1506 | + call terminal_write(char(27) // '[48;5;234m ' // char(27) // '[0m') |
| | 1507 | + else if (is_current_line) then |
| | 1508 | + call terminal_write(char(27) // '[48;5;237m ' // char(27) // '[0m') |
| | 1509 | + else |
| | 1510 | + call terminal_write(' ') |
| 1480 | end if | 1511 | end if |
| | 1512 | + display_col = display_col + 1 |
| 1481 | end do | 1513 | end do |
| 1482 | | 1514 | |
| 1483 | ! Reset attributes | 1515 | ! Reset attributes |
| 1484 | call terminal_write(char(27) // '[0m') | 1516 | call terminal_write(char(27) // '[0m') |
| | 1517 | + |
| | 1518 | + if (allocated(utf8_ch)) deallocate(utf8_ch) |
| 1485 | end subroutine render_buffer_line_in_pane | 1519 | end subroutine render_buffer_line_in_pane |
| 1486 | | 1520 | |
| 1487 | subroutine render_pane_separator(col, start_row, height) | 1521 | subroutine render_pane_separator(col, start_row, height) |
@@ -1879,13 +1913,11 @@ contains |
| 1879 | end if | 1913 | end if |
| 1880 | case ("symbols") | 1914 | case ("symbols") |
| 1881 | if (is_symbols_panel_visible(editor%symbols_panel)) then | 1915 | if (is_symbols_panel_visible(editor%symbols_panel)) then |
| 1882 | - call render_lsp_symbols_panel(editor%symbols_panel, panel_start_col, panel_width, & | 1916 | + call render_lsp_symbols_panel(editor%symbols_panel, editor%screen_rows - 1) |
| 1883 | - 2, editor%screen_rows - 1) | | |
| 1884 | end if | 1917 | end if |
| 1885 | case ("workspace_symbols") | 1918 | case ("workspace_symbols") |
| 1886 | if (is_workspace_symbols_panel_visible(editor%workspace_symbols_panel)) then | 1919 | if (is_workspace_symbols_panel_visible(editor%workspace_symbols_panel)) then |
| 1887 | - call render_lsp_workspace_symbols_panel(editor%workspace_symbols_panel, panel_start_col, & | 1920 | + call render_lsp_workspace_symbols_panel(editor%workspace_symbols_panel, editor%screen_rows - 1) |
| 1888 | - panel_width, 2, editor%screen_rows - 1) | | |
| 1889 | end if | 1921 | end if |
| 1890 | end select | 1922 | end select |
| 1891 | | 1923 | |
@@ -2130,25 +2162,25 @@ contains |
| 2130 | end subroutine render_lsp_references_panel | 2162 | end subroutine render_lsp_references_panel |
| 2131 | | 2163 | |
| 2132 | ! Render symbols panel in offcanvas mode (right side, full height) | 2164 | ! Render symbols panel in offcanvas mode (right side, full height) |
| 2133 | - subroutine render_lsp_symbols_panel(panel, start_col, width, start_row, end_row) | 2165 | + subroutine render_lsp_symbols_panel(panel, screen_height) |
| 2134 | use symbols_panel_module, only: symbols_panel_t, render_symbols_panel | 2166 | use symbols_panel_module, only: symbols_panel_t, render_symbols_panel |
| 2135 | type(symbols_panel_t), intent(in) :: panel | 2167 | type(symbols_panel_t), intent(in) :: panel |
| 2136 | - integer, intent(in) :: start_col, width, start_row, end_row | 2168 | + integer, intent(in) :: screen_height |
| 2137 | | 2169 | |
| 2138 | ! Delegate to the real symbols panel renderer | 2170 | ! Delegate to the real symbols panel renderer |
| 2139 | ! The panel manages its own positioning via panel_start_col and panel_width | 2171 | ! The panel manages its own positioning via panel_start_col and panel_width |
| 2140 | - call render_symbols_panel(panel, end_row) | 2172 | + call render_symbols_panel(panel, screen_height) |
| 2141 | end subroutine render_lsp_symbols_panel | 2173 | end subroutine render_lsp_symbols_panel |
| 2142 | | 2174 | |
| 2143 | ! Render workspace symbols panel in offcanvas mode (right side, full height) | 2175 | ! Render workspace symbols panel in offcanvas mode (right side, full height) |
| 2144 | - subroutine render_lsp_workspace_symbols_panel(panel, start_col, width, start_row, end_row) | 2176 | + subroutine render_lsp_workspace_symbols_panel(panel, screen_height) |
| 2145 | use workspace_symbols_panel_module, only: workspace_symbols_panel_t, render_workspace_symbols_panel | 2177 | use workspace_symbols_panel_module, only: workspace_symbols_panel_t, render_workspace_symbols_panel |
| 2146 | type(workspace_symbols_panel_t), intent(in) :: panel | 2178 | type(workspace_symbols_panel_t), intent(in) :: panel |
| 2147 | - integer, intent(in) :: start_col, width, start_row, end_row | 2179 | + integer, intent(in) :: screen_height |
| 2148 | | 2180 | |
| 2149 | ! Delegate to the real workspace symbols panel renderer | 2181 | ! Delegate to the real workspace symbols panel renderer |
| 2150 | ! The panel manages its own positioning via panel_start_col and panel_width | 2182 | ! The panel manages its own positioning via panel_start_col and panel_width |
| 2151 | - call render_workspace_symbols_panel(panel, end_row) | 2183 | + call render_workspace_symbols_panel(panel, screen_height) |
| 2152 | end subroutine render_lsp_workspace_symbols_panel | 2184 | end subroutine render_lsp_workspace_symbols_panel |
| 2153 | | 2185 | |
| 2154 | ! Helper function to extract basename from path | 2186 | ! Helper function to extract basename from path |