view runtime/autoload/racket.vim @ 34232:47385c831d92 v9.1.0061

patch 9.1.0061: UX of visual highlighting can be improved Commit: https://github.com/vim/vim/commit/e6d8b4662ddf9356da53f56e363b67b524fd8825 Author: Christian Brabandt <cb@256bit.org> Date: Sun Jan 28 23:33:29 2024 +0100 patch 9.1.0061: UX of visual highlighting can be improved Problem: UX of visual highlighting can be improved Solution: Improve readibility of visual highlighting, by setting better foreground and background colors The default visual highlighting currently is nice in that it overlays the actual syntax highlighting by using a separate distinct background color. However, this can cause hard to read text, because the contrast between the actual syntax element and the background color is way too low. That is an issue, that has been bothering colorschemes authors for quite some time so much, that they are defining the Visual highlighting group to use a separate foreground and background color, so that the syntax highlighting vanishes, but the text remains readable (ref: vim/colorschemes#250) So this is an attempt to perform the same fix for the default Visual highlighting and just use a default foreground and background color instead of using reverse. I also removed the hard-coded changes to the Visual highlighting in init_highlight. It's not quite clear to me, why those were there and not added directly to the highlighting_init_<dark|light> struct. closes: #13663 related: vim/colorschemes#250 Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sun, 28 Jan 2024 23:39:23 +0100
parents e0535b3b9d77
children
line wrap: on
line source

" Maintainer: D. Ben Knoble <ben.knoble+github@gmail.com>
" URL: https://github.com/benknoble/vim-racket
" Last Change: 2023 Sep 22
vim9script

def MakePatternFromLiterals(xs: list<string>): string
  return printf('\V%s', xs->mapnew((_, v) => escape(v, '\'))->join('\|'))
enddef

const openers = ['(', '[', '{']
const closers = {'(': ')', '[': ']', '{': '}'}
const brackets_pattern: string = closers->items()->flattennew()->MakePatternFromLiterals()

# transliterated from a modified copy of src/indent.c

export def Indent(): number
  if InHerestring(v:lnum)
    return -1
  endif
  # Indent from first column to avoid odd results from nested forms.
  cursor(v:lnum, 1)
  const bracket = FindBracket()
  if bracket == null_dict || !bracket.found
    return -1
  endif

  # assert_report(printf('{lnum: %d, str: %s, found: %s, line: %d, column: %d}',
  #   v:lnum, getline(bracket.line)[bracket.column - 1], bracket.found, bracket.line, bracket.column))
  # N.B. Column =/= Line Index; Columns start at 1
  const amount: number = bracket.column
  const line = getline(bracket.line)

  const lw = Lispword(line[bracket.column :])
  if !IsForFold(lw) # skip: see comments about for/fold special case below
    # "Extra trick"
    var current = prevnonblank(v:lnum - 1)
    while current > bracket.line
      cursor(current, 1)
      if getline(current) !~# '^\s*;' && synID(current, 1, 0)->synIDattr('name') !~? 'string' && FindBracket() == bracket
        return indent(current)
      endif
      current = prevnonblank(current - 1)
    endwhile
    cursor(v:lnum, 1)
  endif

  if index(openers, line[bracket.column - 1]) >= 0 && !empty(lw)
    # Special case for/fold &co. The iterator clause (2nd form) is indented
    # under the accumulator clause (1st form). Everything else is standard.
    const start_of_first_form = match(line[bracket.column :], MakePatternFromLiterals(openers))
    # assert_report(printf('{line: %s}', line))
    # assert_report(printf('{start: %s}', start_of_first_form >= 0 ? line[bracket.column + start_of_first_form :] : '<NULL>'))
    if IsForFold(lw) && IsSecondForm(bracket.line, bracket.column, v:lnum) && start_of_first_form >= 0
      return amount + start_of_first_form
    else
      # Lispword, but not for/fold second form (or first form couldn't be
      # found): indent like define or lambda.
      # 2 extra indent, but subtract 1 for columns starting at 1.
      # Current vim9 doesn't constant fold "x + 2 - 1", so write "x + 1"
      return amount + 1
    endif
  else
    # assert_report(printf('{line: %s}', line[bracket.column :]))
    return amount + IndentForContinuation(bracket.line, bracket.column, line[bracket.column :])
  endif
enddef

def InHerestring(start: number): bool
  return synID(start, col([start, '$']) - 1, 0)->synIDattr('name') =~? 'herestring'
enddef

def FindBracket(): dict<any>
  const paren = FindMatch('(', ')')
  const square = FindMatch('\[', ']')
  const curly = FindMatch('{', '}')
  return null_dict
    ->MatchMax(paren)
    ->MatchMax(square)
    ->MatchMax(curly)
enddef

def Lispword(line: string): string
  # assume keyword on same line as opener
  const word: string = matchstr(line, '^\s*\k\+\>')->trim()
  # assert_report(printf('line: %s; word: %s', line, word))
  # assert_report(&l:lispwords->split(',')->index(word) >= 0 ? 't' : 'f')
  return &l:lispwords->split(',')->index(word) >= 0 ? word : ''
enddef

# line contains everything on line_nr after column
def IndentForContinuation(line_nr: number, column: number, line: string): number
  const end = len(line)
  var indent = match(line, '[^[:space:]]')
  # first word is a string or some other literal (or maybe a form); assume that
  # the current line is outside such a thing
  if indent < end && ['"', '#']->index(line[indent]) >= 0
    return indent
  endif
  if indent < end && ["'", '`']->index(line[indent]) >= 0
    # could be a form or a word. Advance one and see.
    ++indent
  endif
  if indent < end && ['(', '[', '{']->index(line[indent]) >= 0
    # there's a form; assume outside, but need to skip it to see if any others
    cursor(line_nr, column + indent + 1)
    # assert_report(getline(line_nr)[column + indent :])
    normal! %
    const [_, matched_line, matched_col, _, _] = getcursorcharpos()
    if line_nr != matched_line || matched_col == column + indent + 1
      return indent
    endif
    indent = matched_col - column
  endif
  var in_delim: bool
  var quoted: bool
  while indent < end && (line[indent] !~# '\s' || in_delim || quoted)
    if line[indent] == '\' && !in_delim
      quoted = true
    else
      quoted = false
    endif
    if line[indent] == '|' && !quoted
      in_delim = !in_delim
    endif
    ++indent
  endwhile
  # not handling newlines in first words
  if quoted || in_delim
    return 0
  endif
  # no other word on this line
  if indent == end
    return 0
  endif
  # find beginning of next word
  indent += match(line[indent :], '[^[:space:]]')
  return indent
enddef

def FindMatch(start: string, end: string): dict<any>
  # TODO too slow…
  # could try replicating C? might have false positives. Or make "100"
  # configurable number: for amounts of indent bodies, we're still fast enough…
  const [linenr, column] = searchpairpos(start, '', end, 'bnzW',
    () =>
      synID(line('.'), col('.'), 0)->synIDattr('name') =~? 'char\|string\|comment',
    line('.') > 100 ? line('.') - 100 : 0)
  if linenr > 0 && column > 0
    return {found: true, line: linenr, column: column}
  else
    return {found: false, line: linenr, column: column}
  endif
enddef

def MatchMax(left: dict<any>, right: dict<any>): dict<any>
  if left == null_dict || !left.found
    return right
  endif
  if right == null_dict || !right.found
    return left
  endif
  # left and right non-null, both found
  return PosLT(left, right) ? right : left
enddef

def PosLT(left: dict<any>, right: dict<any>): bool
  return left.line != right.line
     \ ? left.line < right.line
     \ : (left.column != right.column && left.column < right.column)
enddef

def IsForFold(word: string): bool
  return ['for/fold', 'for/foldr', 'for*/fold', 'for*/foldr']->index(word) >= 0
enddef

def IsSecondForm(blnum: number, bcol: number, vlnum: number): bool
  var forms_seen: number # "top-level" (inside for/fold) counter only
  var [lnum, col] = [blnum, bcol + 1]
  cursor(lnum, col)
  var stack: list<string> = []

  while lnum <= vlnum
    const found = search(brackets_pattern, '', vlnum, 0, () =>
      synID(line('.'), col('.'), 0)->synIDattr('name') =~? 'char\|string\|comment')
    if found <= 0
      break
    endif
    const pos = getcursorcharpos()
    lnum = pos[1]
    col = pos[2]
    var current_char = getline(lnum)[col - 1]
    # assert_report(printf('search: %d, %d: %s', lnum, col, current_char))
    # assert_report(printf('forms seen post-search: %d', forms_seen))
    if index(openers, current_char) >= 0
      insert(stack, current_char)
    elseif !empty(stack) && current_char ==# closers[stack[0]]
      stack = stack[1 :]
      if empty(stack)
        ++forms_seen
      endif
    else
      # parse failure of some kind: not an opener or not the correct closer
      return false
    endif
    # assert_report(printf('forms seen pre-check: %d', forms_seen))
    if forms_seen > 2
      return false
    endif
  endwhile

  # assert_report(printf('forms seen pre-return: %d', forms_seen))
  return forms_seen == 2 || (forms_seen == 1 && !empty(stack))
enddef