Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ require("ninetyfive").setup({
-- When `true`, enables the plugin on NeoVim startup
enable_on_startup = true,

-- When `true`, disables 'ghost text' suggestions from NinetyFive
use_cmp = false,
-- Controls nvim-cmp integration: "auto" disables ghost text when the cmp "ninetyfive" source
-- is configured, set true to force cmp-only mode, or false for inline hints
use_cmp = "auto",

-- Update server URI, mostly for debugging
server = "wss://api.ninetyfive.gg",
Expand Down Expand Up @@ -259,13 +260,16 @@ cmp.setup({
})
```

Additionally, you can disable inline suggestions in NinetyFive's setup:
Inline suggestions are disabled automatically when the NinetyFive cmp source is
configured, but you can force cmp-only mode in NinetyFive's setup:
```lua
require("ninetyfive").setup({
use_cmp = true
})
```

Set `use_cmp = false` to always show inline suggestions even with the cmp source enabled.

## Development

```bash
Expand Down
4 changes: 2 additions & 2 deletions doc/ninetyfive.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ Default values:
enable_on_startup = true,
-- Update server URI, mostly for debugging
server = "wss://api.ninetyfive.gg",
-- When true, indicates Ninetyfive is used exclusively as a cmp source
use_cmp = false,
-- Controls cmp integration: "auto" (default), true, or false
use_cmp = "auto",
mappings = {
-- Sets a global mapping to accept a suggestion
accept = "<Tab>",
Expand Down
14 changes: 1 addition & 13 deletions lua/ninetyfive/cmp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@ local Completion = require("ninetyfive.completion")
local util = require("ninetyfive.util")
local config = require("ninetyfive.config")

local function current_config()
if _G.Ninetyfive and _G.Ninetyfive.config then
return _G.Ninetyfive.config
end
return config.options or {}
end

local function is_cmp_enabled()
local cfg = current_config()
return cfg.use_cmp == true
end

local Source = {}
Source.__index = Source

Expand Down Expand Up @@ -90,7 +78,7 @@ function Source:get_keyword_pattern()
end

function Source:is_available()
return vim ~= nil and vim.fn ~= nil and is_cmp_enabled()
return vim ~= nil and vim.fn ~= nil and config.should_use_cmp_mode()
end

function Source:_prepare_context(params)
Expand Down
14 changes: 12 additions & 2 deletions lua/ninetyfive/communication.lua
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,21 @@ function Communication:send_file_content(opts)

local ok, message = pcall(vim.json.encode, payload)
if not ok then
log.debug("comm", "failed to encode file-content payload: %s", tostring(message))
log.debug(
"comm",
"failed to encode file-content payload: %s",
tostring(message)
)
return
end

log.debug("comm", "-> [file-content] path=%s, len=%d, text=%q", path, #content, content)
log.debug(
"comm",
"-> [file-content] path=%s, len=%d, text=%q",
path,
#content,
content
)

if not websocket.send_message(message) then
log.debug("comm", "failed to send file-content for %s", bufname)
Expand Down
101 changes: 99 additions & 2 deletions lua/ninetyfive/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,84 @@ local Completion = require("ninetyfive.completion")

local Ninetyfive = {}

local function normalize_use_cmp(value)
if value == nil then
return "auto"
end

if type(value) == "boolean" then
return value
end

if type(value) == "string" then
local normalized = value:lower()
if normalized == "auto" then
return "auto"
elseif normalized == "true" then
return true
elseif normalized == "false" then
return false
end
end

error('`use_cmp` must be one of: true, false, or "auto".')
end

local function get_runtime_config()
if _G.Ninetyfive and _G.Ninetyfive.config then
return _G.Ninetyfive.config
end
return Ninetyfive.options or {}
end

local function contains_ninetyfive_source(sources)
if type(sources) ~= "table" then
return false
end

for _, source in ipairs(sources) do
if type(source) == "table" then
if source.name == "ninetyfive" then
return true
end

if contains_ninetyfive_source(source) then
return true
end
end
end

return false
end

local function has_configured_cmp_source()
local ok, cmp = pcall(require, "cmp")
if not ok or not cmp then
return false
end

if type(cmp.get_config) ~= "function" then
return false
end

local ok_config, cmp_config = pcall(cmp.get_config)
if not ok_config or type(cmp_config) ~= "table" then
return false
end

local sources = cmp_config.sources
if type(sources) == "function" then
local ok_sources, resolved_sources = pcall(sources)
if ok_sources then
sources = resolved_sources
else
sources = nil
end
end

return contains_ninetyfive_source(sources)
end

--- Ninetyfive configuration with its default values.
---
---@type table
Expand All @@ -15,8 +93,8 @@ Ninetyfive.options = {
enable_on_startup = true,
-- Update server URI, mostly for debugging
server = "wss://api.ninetyfive.gg",
-- When true, indicates Ninetyfive is used exclusively as a cmp source
use_cmp = false,
-- Controls cmp integration: "auto" (default), true, or false
use_cmp = "auto",
mappings = {
-- Sets a global mapping to accept a suggestion
accept = "<Tab>",
Expand Down Expand Up @@ -47,6 +125,8 @@ local defaults = vim.deepcopy(Ninetyfive.options)
function Ninetyfive.defaults(options)
Ninetyfive.options = vim.deepcopy(vim.tbl_deep_extend("keep", options or {}, defaults or {}))

Ninetyfive.options.use_cmp = normalize_use_cmp(Ninetyfive.options.use_cmp)

-- let your user know that they provided a wrong value, this is reported when your plugin is executed.
assert(
type(Ninetyfive.options.debug) == "boolean",
Expand Down Expand Up @@ -117,4 +197,21 @@ function Ninetyfive.setup(options)
return Ninetyfive.options
end

function Ninetyfive.should_use_cmp_mode()
local cfg = get_runtime_config()
local use_cmp = cfg.use_cmp

if use_cmp == true then
return true
elseif use_cmp == false then
return false
elseif type(use_cmp) == "string" and use_cmp:lower() == "true" then
return true
elseif type(use_cmp) == "string" and use_cmp:lower() == "false" then
return false
end

return has_configured_cmp_source()
end

return Ninetyfive
8 changes: 6 additions & 2 deletions lua/ninetyfive/delta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ function M.compute_delta(old_text, new_text)

-- Find common suffix (by byte, don't overlap with prefix)
local j = 0
while j < #old_text - (i - 1) and j < #new_text - (i - 1)
and old_text:sub(#old_text - j, #old_text - j) == new_text:sub(#new_text - j, #new_text - j) do
while
j < #old_text - (i - 1)
and j < #new_text - (i - 1)
and old_text:sub(#old_text - j, #old_text - j)
== new_text:sub(#new_text - j, #new_text - j)
do
j = j + 1
end

Expand Down
29 changes: 19 additions & 10 deletions lua/ninetyfive/highlighting.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ local function blend_color(fg, opacity)
local bg = get_bg_color()
local inv = 1 - opacity

local r = math.floor((math.floor(fg / 0x10000) % 0x100) * opacity + (math.floor(bg / 0x10000) % 0x100) * inv)
local g = math.floor((math.floor(fg / 0x100) % 0x100) * opacity + (math.floor(bg / 0x100) % 0x100) * inv)
local r = math.floor(
(math.floor(fg / 0x10000) % 0x100) * opacity + (math.floor(bg / 0x10000) % 0x100) * inv
)
local g = math.floor(
(math.floor(fg / 0x100) % 0x100) * opacity + (math.floor(bg / 0x100) % 0x100) * inv
)
local b = math.floor((fg % 0x100) * opacity + (bg % 0x100) * inv)

return r * 0x10000 + g * 0x100 + b
Expand Down Expand Up @@ -319,16 +323,19 @@ function M.highlight_completion(completion_text, bufnr)
if i == 1 then
current_hl = hl
elseif hl ~= current_hl then
segments[#segments + 1] = { line_text:sub(current_start, i - 1), get_ghost_highlight(current_hl) }
segments[#segments + 1] =
{ line_text:sub(current_start, i - 1), get_ghost_highlight(current_hl) }
current_start, current_hl = i, hl
end
end

if current_start <= #line_text then
segments[#segments + 1] = { line_text:sub(current_start), get_ghost_highlight(current_hl) }
segments[#segments + 1] =
{ line_text:sub(current_start), get_ghost_highlight(current_hl) }
end

result[#result + 1] = #segments > 0 and segments or { { line_text, get_ghost_highlight(nil) } }
result[#result + 1] = #segments > 0 and segments
or { { line_text, get_ghost_highlight(nil) } }
char_offset = char_offset + #line_text + 1
end

Expand Down Expand Up @@ -411,7 +418,8 @@ function M.highlight_completion_with_matches(completion_text, bufnr, match_posit
current_matched = is_matched
elseif hl ~= current_hl or is_matched ~= current_matched then
local segment_text = first_line:sub(current_start, i - 1)
local segment_hl = current_matched and get_matched_highlight(current_hl) or get_ghost_highlight(current_hl)
local segment_hl = current_matched and get_matched_highlight(current_hl)
or get_ghost_highlight(current_hl)
segments[#segments + 1] = { segment_text, segment_hl }
current_start = i
current_hl = hl
Expand All @@ -421,7 +429,8 @@ function M.highlight_completion_with_matches(completion_text, bufnr, match_posit

if current_start <= #first_line then
local segment_text = first_line:sub(current_start)
local segment_hl = current_matched and get_matched_highlight(current_hl) or get_ghost_highlight(current_hl)
local segment_hl = current_matched and get_matched_highlight(current_hl)
or get_ghost_highlight(current_hl)
segments[#segments + 1] = { segment_text, segment_hl }
end

Expand All @@ -438,22 +447,22 @@ end
-- Returns virt_text format: {{text, hl}, {text, hl}, ...}
function M.highlight_ghost_text(text, bufnr)
if not text or text == "" then
return {{ "", get_ghost_highlight(nil) }}
return { { "", get_ghost_highlight(nil) } }
end

bufnr = bufnr or vim.api.nvim_get_current_buf()
local lang = get_ts_lang(bufnr)

-- Without treesitter, return with default ghost highlight
if not lang then
return {{ text, get_ghost_highlight(nil) }}
return { { text, get_ghost_highlight(nil) } }
end

local prefix, suffix = get_buffer_context(bufnr)
local char_highlights = get_ts_char_highlights(text, lang, prefix, suffix)
local segments = build_segments(text, char_highlights, get_ghost_highlight)

return #segments > 0 and segments or {{ text, get_ghost_highlight(nil) }}
return #segments > 0 and segments or { { text, get_ghost_highlight(nil) } }
end

vim.api.nvim_create_autocmd("ColorScheme", {
Expand Down
7 changes: 5 additions & 2 deletions lua/ninetyfive/http.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ if ok_ffi then
"curl",
}

pcall(ffi.cdef, [[
pcall(
ffi.cdef,
[[
typedef void CURL;
typedef void CURLM;
typedef void CURLSH;
Expand Down Expand Up @@ -79,7 +81,8 @@ if ok_ffi then
CURLMcode curl_multi_socket_action(CURLM *multi, curl_socket_t s, int ev_bitmask, int *running_handles);
CURLMcode curl_multi_assign(CURLM *multi, curl_socket_t s, void *sockp);
struct CURLMsg *curl_multi_info_read(CURLM *multi, int *msgs_in_queue);
]])
]]
)

local function try_load()
for _, name in ipairs(lib_names) do
Expand Down
13 changes: 1 addition & 12 deletions lua/ninetyfive/suggestion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@ local completion_bufnr = nil

local log = require("ninetyfive.util.log")
local lsp_util = vim.lsp.util
local function current_config()
if _G.Ninetyfive and _G.Ninetyfive.config then
return _G.Ninetyfive.config
end
return config.options or {}
end

local function is_cmp_mode_enabled()
local cfg = current_config()
return cfg.use_cmp == true
end

local function trigger_cmp_complete()
local ok, cmp = pcall(require, "cmp")
Expand Down Expand Up @@ -84,7 +73,7 @@ suggestion.show = function(completion)
if vim.fn.mode() ~= "i" then
return
end
local cmp_mode = is_cmp_mode_enabled()
local cmp_mode = config.should_use_cmp_mode()

-- Build text up to the next flush
local parts = {}
Expand Down
Loading
Loading