Repository: neovim/neovim
File: runtime/lua/vim/lsp/buf.lua (Lua)

Code before:

  -- If the selection ends at column 0, adjust the position to the end of the previous line.
  if end_col == 0 then
    end_line = end_line - 1
    local end_line_text = api.nvim_buf_get_lines(0, end_line - 1, end_line, true)[1]
    end_col = #end_line_text
  end

  vim.fn.setpos("'<", { 0, start_line, start_col + 1, 0 })
  vim.fn.setpos("'>", { 0, end_line, end_col, 0 })
  vim.cmd.normal({ 'gv', bang = true })
end

---@param range lsp.Range
local function is_empty(range)
  return range.start.line == range['end'].line and range.start.character == range['end'].character
end

--- Perform an incremental selection at the cursor position based on ranges given by the LSP. The
--- `direction` parameter specifies the number of times to expand the selection. Negative values
--- will shrink the selection. The `use_sync` is optional. When true, the request is performed
--- synchronously. Operator-pending mode requires synchronous execution.
---
--- @param direction integer
--- @param use_sync? boolean
function M.selection_range(direction, use_sync)
  validate('direction', direction, 'number')
  validate('use_sync', use_sync, 'boolean', true)

  local sync = use_sync == true

  if selection_ranges then
    local new_index = selection_ranges.index + direction
    selection_ranges.index = math.min(#selection_ranges.ranges, math.max(1, new_index))

    select_range(selection_ranges.ranges[selection_ranges.index])
    return
  end

  local method = 'textDocument/selectionRange'
  local client = lsp.get_clients({ method = method, bufnr = 0 })[1]
  if not client then
    vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
    return
  end

  local position_params = util.make_position_params(0, client.offset_encoding)

  ---@type lsp.SelectionRangeParams
  local params = {
    textDocument = position_params.textDocument,

Review comment (suggestion):
I think instead of adding a `use_sync` flag this should probably follow the pattern from https://github.com/neovim/neovim/pull/34523 and add a callback to make it `vim.async` ready. Until `vim.async` is ready one can then use the callback to set a flag to block on with `vim.wait`.

Alternatively: Could make this in general synchronous. Not sure if there's much value for it to be async, given that opposed to something like completion it is unusual to continue editing while getting the selection range. 

Code after:

  -- If the selection ends at column 0, adjust the position to the end of the previous line.
  if end_col == 0 then
    end_line = end_line - 1
    local end_line_text = api.nvim_buf_get_lines(0, end_line - 1, end_line, true)[1]
    end_col = #end_line_text
  end

  vim.fn.setpos("'<", { 0, start_line, start_col + 1, 0 })
  vim.fn.setpos("'>", { 0, end_line, end_col, 0 })
  vim.cmd.normal({ 'gv', bang = true })
end

---@param range lsp.Range
local function is_empty(range)
  return range.start.line == range['end'].line and range.start.character == range['end'].character
end

--- Perform an incremental selection at the cursor position based on ranges given by the LSP. The
--- `direction` parameter specifies the number of times to expand the selection. Negative values
--- will shrink the selection.
---
--- @param direction integer
--- @param timeout_ms integer? (default: `1000`) Maximum time (milliseconds) to wait for a result.
function M.selection_range(direction, timeout_ms)
  validate('direction', direction, 'number')
  validate('timeout_ms', timeout_ms, 'number', true)

  if selection_ranges then
    local new_index = selection_ranges.index + direction
    selection_ranges.index = math.min(#selection_ranges.ranges, math.max(1, new_index))

    select_range(selection_ranges.ranges[selection_ranges.index])
    return
  end

  local method = 'textDocument/selectionRange'
  local client = lsp.get_clients({ method = method, bufnr = 0 })[1]
  if not client then
    vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
    return
  end

  local position_params = util.make_position_params(0, client.offset_encoding)

  ---@type lsp.SelectionRangeParams
  local params = {
    textDocument = position_params.textDocument,
    positions = { position_params.position },
  }