Skip to content

Commit

Permalink
Add kcr set-completion command
Browse files Browse the repository at this point in the history
  • Loading branch information
alexherbo2 committed Sep 12, 2021
1 parent 2194c04 commit f196e3c
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2021-09-12

- Add support for [JSON Lines]
- Add `kcr set-completion` command

[JSON Lines]: https://jsonlines.org

Expand Down
74 changes: 74 additions & 0 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,80 @@ See also [Multiple commands] and [Nested commands].
[Multiple commands]: #multiple-commands
[Nested commands]: #nested-commands

###### `set-completion`

**Options**

- `--line <value>` ⇒ Line number
- `--column <value>` ⇒ Column number
- `--length <value>` ⇒ Length value
- `--timestamp <value>` ⇒ Timestamp value

```
kcr set-completion [flags] <name> [input: completions]
```

Set completion [option][options].

[Options]: https://github.com/mawww/kakoune/blob/master/doc/pages/options.asciidoc

**Example**

``` kak
# Declare a new type of completion.
declare-option completions pokemon_completions
# Install the completer.
set-option global completers option=pokemon_completions %opt{completers}
# Declare Pokémon results as JSON.
# Get the first 151 Pokémon.
declare-option str pokemon_results %sh{
curl 'https://pokeapi.co/api/v2/pokemon?limit=151'
}
# Generate completions
define-command pokemon-generate-completions -params 5 -docstring 'pokemon-generate-completions <line> <column> <length> <timestamp> <items>' %{
connect run sh -c %{
echo "$5" |
jq '[.results[] | [.name, [["info", .name]], .name]]' |
kcr set-completion pokemon_completions --line "$1" --column "$2" --length "$3" --timestamp "$4"
} -- %arg{@}
}
# Update completions
hook -group pokemon-completion global InsertIdle '' %{
try %{
# Generate the completions (header and body).
# Execute in a “draft” context, so if we move the cursor it won’t move the “real” cursor.
evaluate-commands -draft %{
# Try to select the entire word before the cursor,
# putting the cursor at the left-end of the selection.
execute-keys 'h<a-i>w<a-;>'
# The selection’s cursor is at the anchor point for completions,
# and the selection covers the text the completions should replace,
# exactly the information we need for the header item.
pokemon-generate-completions %val{cursor_line} %val{cursor_column} %val{selection_length} %val{timestamp} %opt{pokemon_results}
}
} catch %{
# This is not a place to suggest completions,
# so clear our list of completions.
set-option window pokemon_completions
}
}
```

Direct link to [the first 151 Pokémon] results.

[The first 151 Pokémon]: https://pokeapi.co/api/v2/pokemon?limit=151

See also [Intro to Kakoune completions].

[Intro to Kakoune completions]: https://zork.net/~st/jottings/Intro_to_Kakoune_completions.html

###### `help`

```
Expand Down
1 change: 1 addition & 0 deletions src/kakoune.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "./kakoune/arguments"
require "./kakoune/buffer"
require "./kakoune/client"
require "./kakoune/command-builder"
require "./kakoune/completion-builder"
require "./kakoune/commands"
require "./kakoune/context"
require "./kakoune/lib"
Expand Down
6 changes: 0 additions & 6 deletions src/kakoune/arguments.cr
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,4 @@ module Kakoune::Arguments

tokens
end

# Returns a string.
# Escapes Kakoune completion field.
def escape_completion_field(string : String)
string.gsub({ '|' => "\\|", '\\' => "\\\\" })
end
end
38 changes: 37 additions & 1 deletion src/kakoune/cli.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ module Kakoune::CLI
property context = Context.new(session: ENV["KAKOUNE_SESSION"]?, client: ENV["KAKOUNE_CLIENT"]?)
property buffer_names = [] of String
property working_directory : Path?
property position : Position?
property position = Position.new
property length = 0
property timestamp = 0
property raw = false
property lines = false
property stdin = false
Expand Down Expand Up @@ -225,6 +227,26 @@ module Kakoune::CLI
options.command = :escape
end

parser.on("set-completion", "Set completion") do
options.command = :set_completion

parser.on("--line=VALUE", "Line number") do |value|
options.position.line = value.to_i
end

parser.on("--column=VALUE", "Column number") do |value|
options.position.column = value.to_i
end

parser.on("--length=VALUE", "Length value") do |value|
options.length = value.to_i
end

parser.on("--timestamp=VALUE", "Timestamp value") do |value|
options.timestamp = value.to_i
end
end

parser.on("help", "Show help") do
options.command = :help
end
Expand Down Expand Up @@ -520,6 +542,20 @@ module Kakoune::CLI

puts command

when :set_completion
if !context.is_a?(Client)
STDERR.puts "No client in context"
exit(1)
end

name = argv.first

command = CompletionBuilder.build(name, options.position.line, options.position.column, options.length, options.timestamp) do |builder|
builder.add(STDIN)
end

context.send(command)

when :help
option_parser.parse(argv + ["--help"])

Expand Down
85 changes: 85 additions & 0 deletions src/kakoune/completion-builder.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
require "json"
require "./arguments"
require "./command-builder"

class Kakoune::CompletionBuilder
include Arguments

# Aliases
alias Command = Array(String)
alias Candidate = { String, Array(Command), String }

# Properties
property name : String
property line : Int32
property column : Int32
property length : Int32
property timestamp : Int32

# Constructor
property constructor = [] of Candidate

# Creates a new builder.
def initialize(@name, @line, @column, @length, @timestamp)
end

# Creates a new builder, with its configuration specified in the block.
def self.new(name, line, column, length, timestamp, &block)
new(name, line, column, length, timestamp).tap do |builder|
yield builder
end
end

# Builds command from block.
def self.build(name, line, column, length, timestamp, &block : self ->)
new(name, line, column, length, timestamp, &block).build
end

# Adds a single candidate.
def add(text : String, command : Array(Command), menu : String)
constructor.push({ text, command, menu })
end

# Adds multiple candidates.
def add(candidates : Array(Candidate))
constructor.concat(candidates)
end

# Adds candidates from a JSON stream.
def add(io : IO)
add(Array(Candidate).from_json(io))
end

# Builds the completion command.
def build
String.build do |string|
string << quote("set-option", "window", name, build_header(line, column, length, timestamp)) << " "

constructor.each do |text, select_command, menu_text|
string << quote(build_candidate(text, select_command, menu_text)) << " "
end
end
end

private def build_header(line, column, length, timestamp)
"#{line}.#{column}+#{length}@#{timestamp}"
end

private def escape_field(field)
field.gsub({ '|' => "\\|", '\\' => "\\\\" })
end

private def build_candidate(text, command, menu)
select_command = CommandBuilder.build do |builder|
builder.add(command)
end

# Escape fields
text = escape_field(text)
select_command = escape_field(select_command)
menu_text = escape_field(menu)

# Build candidate
candidate = "#{text}|#{select_command}|#{menu_text}"
end
end

0 comments on commit f196e3c

Please sign in to comment.