@@ -166,8 +166,12 @@ contains |
| 166 | do while (i <= line_len) | 166 | do while (i <= line_len) |
| 167 | ch = line(i:i) | 167 | ch = line(i:i) |
| 168 | | 168 | |
| | 169 | + ! Check for multiline comment start |
| | 170 | + if (check_multiline_comment_start(highlighter, line, i)) then |
| | 171 | + call process_multiline_comment_start(highlighter, line, tokens, token_count, i) |
| | 172 | + |
| 169 | ! Check for single-line comment | 173 | ! Check for single-line comment |
| 170 | - if (check_comment_start(highlighter, line, i)) then | 174 | + else if (check_comment_start(highlighter, line, i)) then |
| 171 | token_count = token_count + 1 | 175 | token_count = token_count + 1 |
| 172 | tokens(token_count)%type = TOKEN_COMMENT | 176 | tokens(token_count)%type = TOKEN_COMMENT |
| 173 | tokens(token_count)%start_col = i | 177 | tokens(token_count)%start_col = i |
@@ -396,6 +400,22 @@ contains |
| 396 | end if | 400 | end if |
| 397 | end function check_comment_start | 401 | end function check_comment_start |
| 398 | | 402 | |
| | 403 | + function check_multiline_comment_start(highlighter, line, pos) result(res) |
| | 404 | + type(syntax_highlighter_t), intent(in) :: highlighter |
| | 405 | + character(len=*), intent(in) :: line |
| | 406 | + integer, intent(in) :: pos |
| | 407 | + logical :: res |
| | 408 | + integer :: comment_len |
| | 409 | + |
| | 410 | + res = .false. |
| | 411 | + if (highlighter%current_lang%comment_start /= "") then |
| | 412 | + comment_len = len_trim(highlighter%current_lang%comment_start) |
| | 413 | + if (pos + comment_len - 1 <= len(line)) then |
| | 414 | + res = line(pos:pos+comment_len-1) == trim(highlighter%current_lang%comment_start) |
| | 415 | + end if |
| | 416 | + end if |
| | 417 | + end function check_multiline_comment_start |
| | 418 | + |
| 399 | function check_string_start(highlighter, line, pos) result(res) | 419 | function check_string_start(highlighter, line, pos) result(res) |
| 400 | type(syntax_highlighter_t), intent(in) :: highlighter | 420 | type(syntax_highlighter_t), intent(in) :: highlighter |
| 401 | character(len=*), intent(in) :: line | 421 | character(len=*), intent(in) :: line |
@@ -424,7 +444,7 @@ contains |
| 424 | integer, intent(inout) :: token_count, pos | 444 | integer, intent(inout) :: token_count, pos |
| 425 | integer :: i, start_pos, delim_len, line_len | 445 | integer :: i, start_pos, delim_len, line_len |
| 426 | character(len=:), allocatable :: delimiter | 446 | character(len=:), allocatable :: delimiter |
| 427 | - logical :: found_end | 447 | + logical :: found_end, is_multiline |
| 428 | | 448 | |
| 429 | line_len = len(line) | 449 | line_len = len(line) |
| 430 | start_pos = pos | 450 | start_pos = pos |
@@ -443,6 +463,11 @@ contains |
| 443 | end if | 463 | end if |
| 444 | end do | 464 | end do |
| 445 | | 465 | |
| | 466 | + ! Check if this is a multiline-capable delimiter |
| | 467 | + ! Python: """ or ''' (length 3) |
| | 468 | + ! JavaScript/TypeScript: ` (template literals) |
| | 469 | + is_multiline = (len(delimiter) >= 3) .or. (delimiter == '`') |
| | 470 | + |
| 446 | ! Move past opening delimiter | 471 | ! Move past opening delimiter |
| 447 | pos = pos + len(delimiter) | 472 | pos = pos + len(delimiter) |
| 448 | | 473 | |
@@ -473,6 +498,11 @@ contains |
| 473 | | 498 | |
| 474 | ! Handle unclosed string | 499 | ! Handle unclosed string |
| 475 | if (.not. found_end) then | 500 | if (.not. found_end) then |
| | 501 | + if (is_multiline) then |
| | 502 | + ! Enter multiline string mode |
| | 503 | + highlighter%in_multiline_string = .true. |
| | 504 | + highlighter%string_delimiter = delimiter |
| | 505 | + end if |
| 476 | pos = line_len + 1 | 506 | pos = line_len + 1 |
| 477 | end if | 507 | end if |
| 478 | end subroutine process_string | 508 | end subroutine process_string |
@@ -949,13 +979,83 @@ contains |
| 949 | highlighter%enabled = .true. | 979 | highlighter%enabled = .true. |
| 950 | end subroutine load_markdown_syntax | 980 | end subroutine load_markdown_syntax |
| 951 | | 981 | |
| 952 | - ! Stub implementations for multiline handling | 982 | + ! Handle multiline comment start (when we encounter /* on a line) |
| | 983 | + subroutine process_multiline_comment_start(highlighter, line, tokens, token_count, pos) |
| | 984 | + type(syntax_highlighter_t), intent(inout) :: highlighter |
| | 985 | + character(len=*), intent(in) :: line |
| | 986 | + type(token_t), intent(inout) :: tokens(:) |
| | 987 | + integer, intent(inout) :: token_count, pos |
| | 988 | + integer :: start_pos, end_pos, comment_start_len, comment_end_len, line_len |
| | 989 | + |
| | 990 | + line_len = len(line) |
| | 991 | + start_pos = pos |
| | 992 | + comment_start_len = len_trim(highlighter%current_lang%comment_start) |
| | 993 | + comment_end_len = len_trim(highlighter%current_lang%comment_end) |
| | 994 | + |
| | 995 | + ! Skip past the opening delimiter |
| | 996 | + pos = pos + comment_start_len |
| | 997 | + |
| | 998 | + ! Look for closing delimiter on the same line |
| | 999 | + end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end)) |
| | 1000 | + |
| | 1001 | + if (end_pos > 0) then |
| | 1002 | + ! Found closing delimiter on same line - this is a complete comment |
| | 1003 | + end_pos = pos + end_pos - 1 + comment_end_len - 1 |
| | 1004 | + |
| | 1005 | + token_count = token_count + 1 |
| | 1006 | + tokens(token_count)%type = TOKEN_COMMENT |
| | 1007 | + tokens(token_count)%start_col = start_pos |
| | 1008 | + tokens(token_count)%end_col = end_pos |
| | 1009 | + |
| | 1010 | + pos = end_pos + 1 |
| | 1011 | + else |
| | 1012 | + ! Closing delimiter not found - rest of line is comment, enter multiline mode |
| | 1013 | + token_count = token_count + 1 |
| | 1014 | + tokens(token_count)%type = TOKEN_COMMENT |
| | 1015 | + tokens(token_count)%start_col = start_pos |
| | 1016 | + tokens(token_count)%end_col = line_len |
| | 1017 | + |
| | 1018 | + highlighter%in_multiline_comment = .true. |
| | 1019 | + pos = line_len + 1 |
| | 1020 | + end if |
| | 1021 | + end subroutine process_multiline_comment_start |
| | 1022 | + |
| | 1023 | + ! Handle multiline comment continuation (when we start a line in comment mode) |
| 953 | subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos) | 1024 | subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos) |
| 954 | type(syntax_highlighter_t), intent(inout) :: highlighter | 1025 | type(syntax_highlighter_t), intent(inout) :: highlighter |
| 955 | character(len=*), intent(in) :: line | 1026 | character(len=*), intent(in) :: line |
| 956 | type(token_t), intent(inout) :: tokens(:) | 1027 | type(token_t), intent(inout) :: tokens(:) |
| 957 | integer, intent(inout) :: token_count, pos | 1028 | integer, intent(inout) :: token_count, pos |
| 958 | - ! TODO: Implement multiline comment handling | 1029 | + integer :: end_pos, comment_end_len, line_len |
| | 1030 | + |
| | 1031 | + line_len = len(line) |
| | 1032 | + comment_end_len = len_trim(highlighter%current_lang%comment_end) |
| | 1033 | + |
| | 1034 | + ! Look for closing delimiter |
| | 1035 | + end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end)) |
| | 1036 | + |
| | 1037 | + if (end_pos > 0) then |
| | 1038 | + ! Found closing delimiter on this line |
| | 1039 | + end_pos = pos + end_pos - 1 + comment_end_len - 1 |
| | 1040 | + |
| | 1041 | + token_count = token_count + 1 |
| | 1042 | + tokens(token_count)%type = TOKEN_COMMENT |
| | 1043 | + tokens(token_count)%start_col = pos |
| | 1044 | + tokens(token_count)%end_col = end_pos |
| | 1045 | + |
| | 1046 | + ! Exit multiline comment mode |
| | 1047 | + highlighter%in_multiline_comment = .false. |
| | 1048 | + pos = end_pos + 1 |
| | 1049 | + else |
| | 1050 | + ! Closing delimiter not found - entire rest of line is comment |
| | 1051 | + token_count = token_count + 1 |
| | 1052 | + tokens(token_count)%type = TOKEN_COMMENT |
| | 1053 | + tokens(token_count)%start_col = pos |
| | 1054 | + tokens(token_count)%end_col = line_len |
| | 1055 | + |
| | 1056 | + ! Stay in multiline comment mode |
| | 1057 | + pos = line_len + 1 |
| | 1058 | + end if |
| 959 | end subroutine process_multiline_comment | 1059 | end subroutine process_multiline_comment |
| 960 | | 1060 | |
| 961 | subroutine process_multiline_string(highlighter, line, tokens, token_count, pos) | 1061 | subroutine process_multiline_string(highlighter, line, tokens, token_count, pos) |
@@ -963,7 +1063,37 @@ contains |
| 963 | character(len=*), intent(in) :: line | 1063 | character(len=*), intent(in) :: line |
| 964 | type(token_t), intent(inout) :: tokens(:) | 1064 | type(token_t), intent(inout) :: tokens(:) |
| 965 | integer, intent(inout) :: token_count, pos | 1065 | integer, intent(inout) :: token_count, pos |
| 966 | - ! TODO: Implement multiline string handling | 1066 | + integer :: end_pos, delim_len, line_len |
| | 1067 | + |
| | 1068 | + line_len = len(line) |
| | 1069 | + delim_len = len(highlighter%string_delimiter) |
| | 1070 | + |
| | 1071 | + ! Look for closing delimiter |
| | 1072 | + end_pos = index(line(pos:), highlighter%string_delimiter) |
| | 1073 | + |
| | 1074 | + if (end_pos > 0) then |
| | 1075 | + ! Found closing delimiter on this line |
| | 1076 | + end_pos = pos + end_pos - 1 + delim_len - 1 |
| | 1077 | + |
| | 1078 | + token_count = token_count + 1 |
| | 1079 | + tokens(token_count)%type = TOKEN_STRING |
| | 1080 | + tokens(token_count)%start_col = pos |
| | 1081 | + tokens(token_count)%end_col = end_pos |
| | 1082 | + |
| | 1083 | + ! Exit multiline string mode |
| | 1084 | + highlighter%in_multiline_string = .false. |
| | 1085 | + highlighter%string_delimiter = "" |
| | 1086 | + pos = end_pos + 1 |
| | 1087 | + else |
| | 1088 | + ! Closing delimiter not found - entire rest of line is string |
| | 1089 | + token_count = token_count + 1 |
| | 1090 | + tokens(token_count)%type = TOKEN_STRING |
| | 1091 | + tokens(token_count)%start_col = pos |
| | 1092 | + tokens(token_count)%end_col = line_len |
| | 1093 | + |
| | 1094 | + ! Stay in multiline string mode |
| | 1095 | + pos = line_len + 1 |
| | 1096 | + end if |
| 967 | end subroutine process_multiline_string | 1097 | end subroutine process_multiline_string |
| 968 | | 1098 | |
| 969 | end module syntax_highlighter_module | 1099 | end module syntax_highlighter_module |