From 2c9403ef8e6d68e9947769dee2defa6f1fd38a31 Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 14 Mar 2024 05:23:36 +0900 Subject: [PATCH 1/2] :+1: Add type def of `popup_setoptions` and `nvim_win_set_config` --- function/nvim/_generated.ts | 26 --- function/nvim/_manual.ts | 1 + function/nvim/nvim_win_set_config.ts | 29 +++ function/vim/_generated.ts | 48 ---- function/vim/_manual.ts | 3 +- function/vim/popup_setoptions.ts | 336 +++++++++++++++++++++++++++ 6 files changed, 368 insertions(+), 75 deletions(-) create mode 100644 function/nvim/nvim_win_set_config.ts create mode 100644 function/vim/popup_setoptions.ts diff --git a/function/nvim/_generated.ts b/function/nvim/_generated.ts index f4c3378f..aeb70e7b 100644 --- a/function/nvim/_generated.ts +++ b/function/nvim/_generated.ts @@ -4012,32 +4012,6 @@ export function nvim_win_set_width( return denops.call("nvim_win_set_width", ...args); } -/** - * Configures window layout. Currently only for floating and external windows - * (including changing a split window to those layouts). - * - * When reconfiguring a floating window, absent option keys will not be - * changed. `row`/`col` and `relative` must be reconfigured together. - * - * Parameters: - * - **{window}** Window handle, or 0 for current window - * - **{config}** Map defining the window configuration, see `nvim_open_win()` - * - * See also: - * - `nvim_open_win()` - */ -export function nvim_win_set_config( - denops: Denops, - window: unknown, - config: unknown, -): Promise; -export function nvim_win_set_config( - denops: Denops, - ...args: unknown[] -): Promise { - return denops.call("nvim_win_set_config", ...args); -} - /** * Removes a tab-scoped (t:) variable * diff --git a/function/nvim/_manual.ts b/function/nvim/_manual.ts index be3c45dc..f4c033ed 100644 --- a/function/nvim/_manual.ts +++ b/function/nvim/_manual.ts @@ -1,2 +1,3 @@ export * from "./nvim_open_win.ts"; export * from "./nvim_win_get_config.ts"; +export * from "./nvim_win_set_config.ts"; diff --git a/function/nvim/nvim_win_set_config.ts b/function/nvim/nvim_win_set_config.ts new file mode 100644 index 00000000..a463bcd4 --- /dev/null +++ b/function/nvim/nvim_win_set_config.ts @@ -0,0 +1,29 @@ +import type { Denops } from "https://deno.land/x/denops_core@v6.0.5/mod.ts"; + +import type { NvimOpenWinConfig } from "./nvim_open_win.ts"; + +/** + * Configures window layout. Currently only for floating and external windows + * (including changing a split window to those layouts). + * + * When reconfiguring a floating window, absent option keys will not be + * changed. `row`/`col` and `relative` must be reconfigured together. + * + * Parameters: + * - **{window}** Window handle, or 0 for current window + * - **{config}** Map defining the window configuration, see `nvim_open_win()` + * + * See also: + * - `nvim_open_win()` + */ +export function nvim_win_set_config( + denops: Denops, + window: number, + config: Partial, +): Promise; +export function nvim_win_set_config( + denops: Denops, + ...args: unknown[] +): Promise { + return denops.call("nvim_win_set_config", ...args); +} diff --git a/function/vim/_generated.ts b/function/vim/_generated.ts index 1a82b6e6..116ce0ff 100644 --- a/function/vim/_generated.ts +++ b/function/vim/_generated.ts @@ -2774,54 +2774,6 @@ export function popup_notification( return denops.call("popup_notification", ...args); } -/** - * Override options in popup **{id}** with entries in **{options}**. - * These options can be set: - * border - * borderchars - * borderhighlight - * callback - * close - * cursorline - * drag - * filter - * firstline - * flip - * highlight - * mapping - * mask - * moved - * padding - * resize - * scrollbar - * scrollbarhighlight - * thumbhighlight - * time - * title - * wrap - * zindex - * The options from `popup_move()` can also be used. - * Generally, setting an option to zero or an empty string resets - * it to the default value, but there are exceptions. - * For "hidden" use `popup_hide()` and `popup_show()`. - * "tabpage" cannot be changed. - * - * Can also be used as a `method`: - * - * GetPopup()->popup_setoptions(options) - */ -export function popup_setoptions( - denops: Denops, - id: unknown, - options: unknown, -): Promise; -export function popup_setoptions( - denops: Denops, - ...args: unknown[] -): Promise { - return denops.call("popup_setoptions", ...args); -} - /** * Set the text of the buffer in popup win **{id}**. **{text}** is the * same as supplied to `popup_create()`, except that a buffer diff --git a/function/vim/_manual.ts b/function/vim/_manual.ts index b89afbca..09ad650a 100644 --- a/function/vim/_manual.ts +++ b/function/vim/_manual.ts @@ -1,2 +1,3 @@ -export * from "./prop_add_list.ts"; export * from "./popup_create.ts"; +export * from "./popup_setoptions.ts"; +export * from "./prop_add_list.ts"; diff --git a/function/vim/popup_setoptions.ts b/function/vim/popup_setoptions.ts new file mode 100644 index 00000000..8ba72e89 --- /dev/null +++ b/function/vim/popup_setoptions.ts @@ -0,0 +1,336 @@ +import type { Denops } from "../../mod.ts"; + +/** + * Override options in popup **{id}** with entries in **{options}**. + * These options can be set: + * border + * borderchars + * borderhighlight + * callback + * close + * cursorline + * drag + * filter + * firstline + * flip + * highlight + * mapping + * mask + * moved + * padding + * resize + * scrollbar + * scrollbarhighlight + * thumbhighlight + * time + * title + * wrap + * zindex + * The options from `popup_move()` can also be used. + * Generally, setting an option to zero or an empty string resets + * it to the default value, but there are exceptions. + * For "hidden" use `popup_hide()` and `popup_show()`. + * "tabpage" cannot be changed. + * + * Can also be used as a `method`: + * + * GetPopup()->popup_setoptions(options) + */ +export function popup_setoptions( + denops: Denops, + id: number, + options: PopupSetOptionsOptions, +): Promise; +export function popup_setoptions( + denops: Denops, + ...args: unknown[] +): Promise { + return denops.call("popup_setoptions", ...args); +} + +export interface PopupSetOptionsOptions { + /** + * Screen line where to position the popup. Can use a + * number or "cursor", "cursor+1" or "cursor-1" to use + * the line of the cursor and add or subtract a number of + * lines. If omitted or zero the popup is vertically + * centered. The first line is 1. + * When using "textprop" the number is relative to the + * text property and can be negative. + */ + line?: number | string; + + /** + * Screen column where to position the popup. Can use a + * number or "cursor" to use the column of the cursor, + * "cursor+9" or "cursor-9" to add or subtract a number + * of columns. If omitted or zero the popup is + * horizontally centered. The first column is 1. + * When using "textprop" the number is relative to the + * text property and can be negative. + */ + col?: number | string; + + /** + * "topleft", "topright", "botleft" or "botright": + * defines what corner of the popup "line" and "col" are + * used for. When not set "topleft" is used. + * Alternatively "center" can be used to position the + * popup in the center of the Vim window, in which case + * "line" and "col" are ignored. + */ + pos?: "topleft" | "topright" | "botleft" | "botright" | "center"; + + /** + * When FALSE (the default), and: + * - "pos" is "botleft" or "topleft", and + * - "wrap" is off, and + * - the popup would be truncated at the right edge of + * the screen, then + * the popup is moved to the left so as to fit the + * contents on the screen. Set to TRUE to disable this. + */ + fixed?: boolean; + + /** + * When TRUE (the default) and the position is relative + * to the cursor, flip to below or above the cursor to + * avoid overlap with the |popupmenu-completion| or + * another popup with a higher "zindex". When there is + * no space above/below the cursor then show the popup to + * the side of the popup or popup menu. + * {not implemented yet} + */ + flip?: boolean; + + /** + * Maximum height of the contents, excluding border and + * padding. + */ + maxheight?: number; + + /** + * Minimum height of the contents, excluding border and + * padding. + */ + minheight?: number; + + /** + * Maximum width of the contents, excluding border, + * padding and scrollbar. + */ + maxwidth?: number; + + /** + * Minimum width of the contents, excluding border, + * padding and scrollbar. + */ + minwidth?: number; + + /** + * First buffer line to display. When larger than one it + * looks like the text scrolled up. When out of range + * the last buffer line will at the top of the window. + * Set to zero to leave the position as set by commands. + * Also see "scrollbar". + */ + firstline?: number; + + /** + * Text to be displayed above the first item in the + * popup, on top of any border. If there is no top + * border one line of padding is added to put the title + * on. You might want to add one or more spaces at the + * start and end as padding. + */ + title?: string; + + /** + * TRUE to make the lines wrap (default TRUE). + */ + wrap?: boolean; + + /** + * TRUE to allow the popup to be dragged with the mouse + * by grabbing at the border. Has no effect if the + * popup does not have a border. As soon as dragging + * starts and "pos" is "center" it is changed to + * "topleft". + */ + drag?: boolean; + + /** + * TRUE to allow the popup to be resized with the mouse + * by grabbing at the bottom right corner. Has no effect + * if the popup does not have a border. + */ + resize?: boolean; + + /** + * When "button" an X is displayed in the top-right, on + * top of any border, padding or text. When clicked on + * the X the popup will close. Any callback is invoked + * with the value -2. + * When "click" any mouse click in the popup will close + * it. + * When "none" (the default) mouse clicks do not close + * the popup window. + */ + close?: "none" | "click" | "button"; + + /** + * Highlight group name to use for the text, stored in + * the 'wincolor' option. + */ + highlight?: string; + + /** + * List with numbers, defining the padding + * above/right/below/left of the popup (similar to CSS). + * An empty list uses a padding of 1 all around. The + * padding goes around the text, inside any border. + * Padding uses the 'wincolor' highlight. + * Example: [1, 2, 1, 3] has 1 line of padding above, 2 + * columns on the right, 1 line below and 3 columns on + * the left. + */ + padding?: + | readonly [] + | readonly [above: number, right: number, below: number, left: number]; + + /** + * List with numbers, defining the border thickness + * above/right/below/left of the popup (similar to CSS). + * Only values of zero and non-zero are currently + * recognized. An empty list uses a border all around. + */ + border?: + | readonly [] + | readonly [above: number, right: number, below: number, left: number]; + + /** + * List of highlight group names to use for the border. + * When one entry it is used for all borders, otherwise + * the highlight for the top/right/bottom/left border. + * Example: ['TopColor', 'RightColor', 'BottomColor, + * 'LeftColor'] + */ + borderhighlight?: + | readonly [string] + | readonly [top: string, right: string, bottom: string, left: string]; + + /** + * List with characters, defining the character to use + * for the top/right/bottom/left border. Optionally + * followed by the character to use for the + * topleft/topright/botright/botleft corner. + * Example: ['-', '|', '-', '|', '┌', '┐', '┘', '└'] + * When the list has one character it is used for all. + * When the list has two characters the first is used for + * the border lines, the second for the corners. + * By default a double line is used all around when + * 'encoding' is "utf-8" and 'ambiwidth' is "single", + * otherwise ASCII characters are used. + */ + borderchars?: + | readonly [top: string, right: string, bottom: string, left: string] + | readonly [ + top: string, + right: string, + bottom: string, + left: string, + topleft: string, + topright: string, + botright: string, + botleft: string, + ]; + + /** + * 1 or true: show a scrollbar when the text doesn't fit. + * zero: do not show a scrollbar. Default is non-zero. + * Also see `popup-scrollbar`. + */ + scrollbar?: 0 | 1 | true; + + /** + * Highlight group name for the scrollbar. The + * background color is what matters. When not given then + * PmenuSbar is used. + */ + scrollbarhighlight?: string; + + /** + * Highlight group name for the scrollbar thumb. The + * background color is what matters. When not given then + * PmenuThumb is used. + */ + thumbhighlight?: string; + + /** + * Priority for the popup, default 50. Minimum value is + * 1, maximum value is 32000. + */ + zindex?: number; + + /** + * A list of lists with coordinates, defining parts of + * the popup that are transparent. See `popup-mask`. + */ + mask?: unknown; + + /** + * Time in milliseconds after which the popup will close. + * When omitted `popup_close()` must be used. + */ + time?: number; + + /** + * Specifies to close the popup if the cursor moved: + * - "any": if the cursor moved at all + * - "word": if the cursor moved outside `` + * - "WORD": if the cursor moved outside `` + * - "expr": if the cursor moved outside `` + * - [{start}, {end}]: if the cursor moved before column + * {start} or after {end} + * - [{lnum}, {start}, {end}]: if the cursor moved away + * from line {lnum}, before column {start} or after + * {end} + * - [0, 0, 0] do not close the popup when the cursor + * moves + * The popup also closes if the cursor moves to another + * line or to another window. + */ + moved?: + | "any" + | "word" + | "WORD" + | "expr" + | readonly [start: number, end: number] + | readonly [lnum: number, start: number, end: number]; + + /** + * TRUE: Highlight the cursor line. Also scrolls the + * text to show this line (only works properly + * when 'wrap' is off). + * zero: Do not highlight the cursor line. + * Default is zero, except for `popup_menu()`. + */ + cursorline?: 0 | true; + + /** + * A callback that can filter typed characters, see + * `popup-filter`. + * + * **WARNING:** + * Probably this feature doesn't work from Denops because Denops cannot pass Vim script function + * through JSON RPC. + */ + filter?: unknown; + + /** + * Allow for key mapping. When FALSE and the popup is + * visible and has a filter callback key mapping is + * disabled. Default value is TRUE. + */ + mapping?: boolean; +} From 00b5bb588b3accc3e89fa37cdfd9d7f9a1cb825c Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 14 Mar 2024 05:39:09 +0900 Subject: [PATCH 2/2] :+1: Add `config` function to `popup` module --- popup/mod.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ popup/nvim.ts | 38 ++++++++++++++++++++++++++++++++++---- popup/vim.ts | 25 ++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/popup/mod.ts b/popup/mod.ts index 931af7f2..d8ee158c 100644 --- a/popup/mod.ts +++ b/popup/mod.ts @@ -25,6 +25,11 @@ * col: 1, * }); * + * // Config a popup window + * await popup.config(denops, popupWindow.winid, { + * title: "Hello, world!", + * }); + * * // Wiat 3 seconds * await new Promise((resolve) => setTimeout(resolve, 3000)); * @@ -77,10 +82,12 @@ import * as fn from "../function/mod.ts"; import type { OpenOptions, PopupWindow } from "./types.ts"; import { closePopup as closePopupVim, + configPopup as configPopupVim, openPopup as openPopupVim, } from "./vim.ts"; import { closePopup as closePopupNvim, + configPopup as configPopupNvim, openPopup as openPopupNvim, } from "./nvim.ts"; @@ -168,4 +175,42 @@ export async function open( }; } +/** + * Config a popup window in Vim/Neovim compatible way. + * + * ```typescript + * import type { Denops } from "https://deno.land/x/denops_std@$MODULE_VERSION/mod.ts"; + * import * as popup from "https://deno.land/x/denops_std@$MODULE_VERSION/popup/mod.ts"; + * + * export async function main(denops: Denops): Promise { + * // Open a popup window + * await using popupWindow = await popup.open(denops, { + * relative: "editor", + * width: 20, + * height: 20, + * row: 1, + * col: 1, + * }); + * + * // Config a popup window + * await popup.config(denops, popupWindow.winid, { + * title: "Hello, world!", + * }); + * } + * ``` + * + * Note that this function does NOT work in `batch.collect()`. + */ +export async function config( + denops: Denops, + winid: number, + options: Partial>, +): Promise { + const config = denops.meta.host === "vim" ? configPopupVim : configPopupNvim; + await config(denops, winid, options); + if (!options.noRedraw) { + await denops.redraw(); + } +} + export type { OpenOptions, PopupWindow } from "./types.ts"; diff --git a/popup/nvim.ts b/popup/nvim.ts index 280a79df..55aec7d5 100644 --- a/popup/nvim.ts +++ b/popup/nvim.ts @@ -5,7 +5,7 @@ import { ulid } from "https://deno.land/std@0.217.0/ulid/mod.ts"; import type { Border, OpenOptions } from "./types.ts"; -const cacheKey = "denops_std/popup/nvim.ts@1"; +const cacheKey = "denops_std/popup/nvim.ts@2"; async function ensurePrerequisites(denops: Denops): Promise { if (typeof denops.context[cacheKey] === "string") { @@ -21,6 +21,12 @@ async function ensurePrerequisites(denops: Denops): Promise { endif return winid endfunction + function! DenopsStdPopupNvimWinSetConfig_${suffix}(winid, config, winhighlight) abort + call nvim_win_set_config(a:winid, a:config) + if a:winhighlight isnot v:null + call nvim_win_set_option(winid, 'winhighlight', a:winhighlight) + endif + endfunction `; await execute(denops, script); return suffix; @@ -42,12 +48,36 @@ export async function openPopup( ) as number; } +export async function configPopup( + denops: Denops, + winid: number, + options: Partial>, +): Promise { + const suffix = await ensurePrerequisites(denops); + const nvimWinSetConfig = toNvimWinSetConfig(options); + const winhighlight = toNvimWinhighlight(options.highlight); + await denops.call( + `DenopsStdPopupNvimWinSetConfig_${suffix}`, + winid, + nvimWinSetConfig, + winhighlight, + ); +} + export async function closePopup(denops: Denops, winid: number): Promise { await nvimFn.nvim_win_close(denops, winid, true); } -function toNvimOpenWinConfig(options: OpenOptions): nvimFn.NvimOpenWinConfig { - const v: nvimFn.NvimOpenWinConfig = { +function toNvimOpenWinConfig( + options: Omit, +): nvimFn.NvimOpenWinConfig { + return toNvimWinSetConfig(options) as nvimFn.NvimOpenWinConfig; +} + +function toNvimWinSetConfig( + options: Partial>, +): Partial { + const v: Partial = { relative: options.relative, anchor: options.anchor, width: options.width, @@ -63,7 +93,7 @@ function toNvimOpenWinConfig(options: OpenOptions): nvimFn.NvimOpenWinConfig { Object .entries(v) .filter(([, v]) => v != undefined), - ) as nvimFn.NvimOpenWinConfig; + ) as Partial; } function toNvimBorder( diff --git a/popup/vim.ts b/popup/vim.ts index cb00237a..f041650e 100644 --- a/popup/vim.ts +++ b/popup/vim.ts @@ -15,16 +15,35 @@ export function openPopup( >; } +export function configPopup( + denops: Denops, + winid: number, + options: Partial>, +): Promise { + const popupSetOptionsOptions = toPopupSetOptionsOptions(options); + return vimFn.popup_setoptions(denops, winid, popupSetOptionsOptions); +} + export async function closePopup(denops: Denops, winid: number): Promise { await vimFn.popup_close(denops, winid); } -function toPopupCreateOptions(options: OpenOptions): vimFn.PopupCreateOptions { +function toPopupCreateOptions( + options: Omit, +): vimFn.PopupCreateOptions { + return { + ...toPopupSetOptionsOptions(options), + posinvert: false, // To keep consistent with the behavior of Neovim's floating window + }; +} + +function toPopupSetOptionsOptions( + options: Partial>, +): vimFn.PopupSetOptionsOptions { const v: vimFn.PopupCreateOptions = { line: options.row, col: options.col, pos: options.anchor ? posFromAnchor(options.anchor) : undefined, - posinvert: false, // To keep consistent with the behavior of Neovim's floating window fixed: true, // To keep consistent with the behavior of Neovim's floating window flip: false, // To keep consistent with the behavior of Neovim's floating window maxheight: options.height, @@ -48,7 +67,7 @@ function toPopupCreateOptions(options: OpenOptions): vimFn.PopupCreateOptions { Object .entries(v) .filter(([, v]) => v != undefined), - ) as vimFn.PopupCreateOptions; + ) as vimFn.PopupSetOptionsOptions; } function posFromAnchor(anchor: Anchor): vimFn.PopupCreateOptions["pos"] {