charming 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/charming/application.rb +3 -3
- data/lib/charming/controller/class_methods.rb +2 -2
- data/lib/charming/controller/command_palette.rb +2 -2
- data/lib/charming/controller/rendering.rb +2 -2
- data/lib/charming/controller/session_state.rb +1 -1
- data/lib/charming/generators/component_generator.rb +1 -1
- data/lib/charming/generators/templates/app/application.template +1 -1
- data/lib/charming/generators/templates/app/layout.template +3 -6
- data/lib/charming/generators/templates/app/view.template +1 -1
- data/lib/charming/generators/templates/component/component.rb.template +1 -1
- data/lib/charming/generators/templates/screen/view.rb.template +1 -1
- data/lib/charming/generators/templates/view/view.rb.template +1 -1
- data/lib/charming/internal/renderer/differential.rb +13 -5
- data/lib/charming/internal/terminal/tty_backend.rb +22 -2
- data/lib/charming/presentation/component.rb +3 -5
- data/lib/charming/presentation/components/activity_indicator.rb +173 -134
- data/lib/charming/presentation/components/command_palette.rb +94 -96
- data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
- data/lib/charming/presentation/components/empty_state.rb +47 -49
- data/lib/charming/presentation/components/form/builder.rb +52 -54
- data/lib/charming/presentation/components/form/confirm.rb +49 -51
- data/lib/charming/presentation/components/form/field.rb +94 -96
- data/lib/charming/presentation/components/form/input.rb +53 -55
- data/lib/charming/presentation/components/form/note.rb +27 -29
- data/lib/charming/presentation/components/form/select.rb +84 -86
- data/lib/charming/presentation/components/form/textarea.rb +67 -69
- data/lib/charming/presentation/components/form.rb +120 -122
- data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
- data/lib/charming/presentation/components/list.rb +123 -125
- data/lib/charming/presentation/components/markdown.rb +21 -23
- data/lib/charming/presentation/components/modal.rb +46 -48
- data/lib/charming/presentation/components/progressbar.rb +51 -53
- data/lib/charming/presentation/components/spinner.rb +40 -42
- data/lib/charming/presentation/components/table.rb +109 -111
- data/lib/charming/presentation/components/text_area.rb +219 -221
- data/lib/charming/presentation/components/text_input.rb +120 -122
- data/lib/charming/presentation/components/viewport.rb +218 -220
- data/lib/charming/presentation/layout/builder.rb +64 -66
- data/lib/charming/presentation/layout/overlay.rb +48 -50
- data/lib/charming/presentation/layout/pane.rb +122 -118
- data/lib/charming/presentation/layout/rect.rb +14 -16
- data/lib/charming/presentation/layout/screen_layout.rb +40 -42
- data/lib/charming/presentation/layout/split.rb +101 -103
- data/lib/charming/presentation/layout.rb +28 -30
- data/lib/charming/presentation/markdown/block_renderers.rb +94 -96
- data/lib/charming/presentation/markdown/inline_renderers.rb +52 -54
- data/lib/charming/presentation/markdown/render_context.rb +12 -14
- data/lib/charming/presentation/markdown/renderer.rb +84 -86
- data/lib/charming/presentation/markdown/syntax_highlighter.rb +57 -59
- data/lib/charming/presentation/markdown.rb +4 -6
- data/lib/charming/presentation/template_view.rb +22 -24
- data/lib/charming/presentation/templates/erb_handler.rb +4 -6
- data/lib/charming/presentation/templates.rb +47 -49
- data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
- data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
- data/lib/charming/presentation/ui/border.rb +24 -26
- data/lib/charming/presentation/ui/border_painter.rb +37 -39
- data/lib/charming/presentation/ui/canvas.rb +59 -61
- data/lib/charming/presentation/ui/style.rb +173 -175
- data/lib/charming/presentation/ui/theme.rb +133 -135
- data/lib/charming/presentation/ui/width.rb +12 -14
- data/lib/charming/presentation/ui.rb +69 -71
- data/lib/charming/presentation/view.rb +103 -105
- data/lib/charming/runtime.rb +23 -10
- data/lib/charming/version.rb +1 -1
- data/lib/charming.rb +3 -2
- metadata +2 -1
|
@@ -1,84 +1,82 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
4
|
+
module Components
|
|
5
|
+
class Form
|
|
6
|
+
# Textarea is a multi-line Form field backed by a TextArea widget. The cursor offset,
|
|
7
|
+
# top-visible row, and preferred vertical column are all persisted in the form's
|
|
8
|
+
# per-field state so the field behaves consistently when refocused mid-edit.
|
|
9
|
+
class Textarea < Field
|
|
10
|
+
# *value* is the initial text. *placeholder* is shown when the value is empty.
|
|
11
|
+
# *width* and *height* constrain the rendered area. All other options are forwarded
|
|
12
|
+
# to Field (label, required, validate, help, theme).
|
|
13
|
+
def initialize(name, value: "", placeholder: "", width: nil, height: nil, **options)
|
|
14
|
+
super(name, **options)
|
|
15
|
+
@initial_value = value
|
|
16
|
+
@placeholder = placeholder
|
|
17
|
+
@width = width
|
|
18
|
+
@height = height
|
|
19
|
+
end
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
# Binds the field, seeds the initial value, and initializes the cursor/offset state.
|
|
22
|
+
def bind(state)
|
|
23
|
+
super
|
|
24
|
+
state[:values][name] = @initial_value if state[:values][name].nil?
|
|
25
|
+
field_state[:cursor] = state[:values][name].to_s.length unless field_state.key?(:cursor)
|
|
26
|
+
field_state[:offset] ||= 0
|
|
27
|
+
end
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
# Forwards key events to the underlying TextArea, syncing the value, cursor, offset,
|
|
30
|
+
# and preferred column back into the form state. Returns :handled when consumed.
|
|
31
|
+
def handle_key(event)
|
|
32
|
+
area = text_area
|
|
33
|
+
result = area.handle_key(event)
|
|
34
|
+
return nil unless result == :handled
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
state[:values][name] = area.value
|
|
37
|
+
field_state[:cursor] = area.cursor
|
|
38
|
+
field_state[:offset] = area.offset
|
|
39
|
+
field_state[:preferred_column] = area.preferred_column
|
|
40
|
+
:handled
|
|
41
|
+
end
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
# Renders the field with its label on the first line, body lines indented, and
|
|
44
|
+
# optional help/error lines below.
|
|
45
|
+
def render(active: false)
|
|
46
|
+
label_line = "#{active ? ">" : " "} #{label}:"
|
|
47
|
+
label_line = theme.selected.render(label_line) if active
|
|
48
|
+
[label_line, *body_lines, help_line, *error_lines].compact.join("\n")
|
|
49
|
+
end
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
private
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
# The default value for a freshly-bound field is the *value* passed at construction.
|
|
54
|
+
def default_value
|
|
55
|
+
@initial_value
|
|
56
|
+
end
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
# Renders the multi-line body, indenting each line by two spaces.
|
|
59
|
+
def body_lines
|
|
60
|
+
text_area.render.lines(chomp: true).map { |line| " #{line}" }
|
|
61
|
+
end
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
# Builds a fresh TextArea each render, seeded from the current form-state value and
|
|
64
|
+
# the persisted cursor/offset/preferred_column.
|
|
65
|
+
def text_area
|
|
66
|
+
TextArea.new(
|
|
67
|
+
value: value.to_s,
|
|
68
|
+
placeholder: @placeholder,
|
|
69
|
+
width: @width,
|
|
70
|
+
height: @height,
|
|
71
|
+
cursor: field_state[:cursor],
|
|
72
|
+
offset: field_state[:offset],
|
|
73
|
+
preferred_column: field_state[:preferred_column]
|
|
74
|
+
)
|
|
75
|
+
end
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
77
|
+
# Returns the per-field state hash for this field.
|
|
78
|
+
def field_state
|
|
79
|
+
state[:fields][name]
|
|
82
80
|
end
|
|
83
81
|
end
|
|
84
82
|
end
|
|
@@ -1,155 +1,153 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
4
|
+
module Components
|
|
5
|
+
# Form is a multi-field form component with built-in focus traversal, validation, and
|
|
6
|
+
# submit/cancel handling. Fields are produced by `Form::Builder` (see `controller.form`)
|
|
7
|
+
# and bound to a per-form mutable state hash. Tab/Shift+Tab cycles focus through
|
|
8
|
+
# focusable fields, Enter advances to the next field (or submits on the last), Escape
|
|
9
|
+
# cancels, and Ctrl+S submits from any field.
|
|
10
|
+
class Form < Component
|
|
11
|
+
# The list of field objects and the mutable state hash the form is bound to.
|
|
12
|
+
attr_reader :fields, :state
|
|
13
|
+
|
|
14
|
+
# *fields* is the array of form field objects. *state* is a hash for storing field
|
|
15
|
+
# values/errors and the current focus index; usually `session[:forms][form_name]`.
|
|
16
|
+
def initialize(fields:, state: nil, theme: nil)
|
|
17
|
+
super(theme: theme)
|
|
18
|
+
@fields = fields
|
|
19
|
+
@state = normalize_state(state || {})
|
|
20
|
+
bind_fields
|
|
21
|
+
clamp_focus
|
|
22
|
+
end
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
# Handles key events: Escape cancels, Ctrl+S submits, Tab cycles focus, Enter advances
|
|
25
|
+
# or submits, and unhandled keys are passed to the focused field.
|
|
26
|
+
def handle_key(event)
|
|
27
|
+
key = Charming.key_of(event)
|
|
28
|
+
return :cancelled if key == :escape
|
|
29
|
+
return submit if submit_shortcut?(event)
|
|
30
|
+
return move_focus(tab_direction(event)) if key == :tab
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
result = handle_current_field(event)
|
|
33
|
+
return result if result
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
advance_or_submit if key == :enter
|
|
36
|
+
end
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
# Returns a hash of `{field_name => value}` for the current field values.
|
|
39
|
+
def values
|
|
40
|
+
state[:values]
|
|
41
|
+
end
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
# Renders each field on its own line, marking the active field with `active: true`.
|
|
44
|
+
def render
|
|
45
|
+
fields.each_with_index.map do |field, index|
|
|
46
|
+
field.render(active: index == state[:focus_index])
|
|
47
|
+
end.join("\n")
|
|
48
|
+
end
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
private
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
# Ensures the state hash has all the required sub-keys: :values, :fields, :errors, and
|
|
53
|
+
# a sensible :focus_index default.
|
|
54
|
+
def normalize_state(value)
|
|
55
|
+
value[:values] ||= {}
|
|
56
|
+
value[:fields] ||= {}
|
|
57
|
+
value[:errors] ||= {}
|
|
58
|
+
value[:focus_index] ||= first_focusable_index || 0
|
|
59
|
+
value
|
|
60
|
+
end
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
# Binds each field to the state hash so field updates write back into `state[:values]`.
|
|
63
|
+
def bind_fields
|
|
64
|
+
fields.each { |field| field.bind(state) }
|
|
65
|
+
end
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
# Forwards *event* to the currently focused field and returns its result.
|
|
68
|
+
def handle_current_field(event)
|
|
69
|
+
current_field&.handle_key(event)
|
|
70
|
+
end
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
# Returns -1 for Shift+Tab (backward), +1 for plain Tab (forward).
|
|
73
|
+
def tab_direction(event)
|
|
74
|
+
return -1 if event.respond_to?(:shift) && event.shift
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
+1
|
|
77
|
+
end
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# True when the event is the submit shortcut (Ctrl+S).
|
|
80
|
+
def submit_shortcut?(event)
|
|
81
|
+
Charming.key_of(event) == :s && event.respond_to?(:ctrl) && event.ctrl
|
|
82
|
+
end
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
# On Enter: submit when the last focusable field is active, otherwise advance focus.
|
|
85
|
+
def advance_or_submit
|
|
86
|
+
return submit if last_focusable?
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
move_focus(+1)
|
|
89
|
+
end
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
# Validates all fields, focuses the first invalid one, and returns [:submitted, values]
|
|
92
|
+
# when there are no errors.
|
|
93
|
+
def submit
|
|
94
|
+
state[:errors] = validation_errors
|
|
95
|
+
focus_first_error unless state[:errors].empty?
|
|
96
|
+
return :handled unless state[:errors].empty?
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
[:submitted, values.dup]
|
|
99
|
+
end
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
end
|
|
101
|
+
# Runs each field's validator and collects per-field error messages.
|
|
102
|
+
def validation_errors
|
|
103
|
+
fields.each_with_object({}) do |field, errors|
|
|
104
|
+
messages = field.validate
|
|
105
|
+
errors[field.name] = messages unless messages.empty?
|
|
108
106
|
end
|
|
107
|
+
end
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
# Moves focus to the first focusable field with errors, when any.
|
|
110
|
+
def focus_first_error
|
|
111
|
+
invalid = fields.index { |field| field.focusable? && state[:errors].key?(field.name) }
|
|
112
|
+
state[:focus_index] = invalid if invalid
|
|
113
|
+
end
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
# Returns the field at the current focus index, or nil when out of range.
|
|
116
|
+
def current_field
|
|
117
|
+
fields[state[:focus_index]]
|
|
118
|
+
end
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
# Moves focus by *direction* (forward or backward) through the focusable fields.
|
|
121
|
+
def move_focus(direction)
|
|
122
|
+
indices = focusable_indices
|
|
123
|
+
return nil if indices.empty?
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
current = indices.index(state[:focus_index]) || 0
|
|
126
|
+
state[:focus_index] = indices[(current + direction) % indices.length]
|
|
127
|
+
:handled
|
|
128
|
+
end
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
# True when the current focus index is the last focusable field.
|
|
131
|
+
def last_focusable?
|
|
132
|
+
focusable_indices.last == state[:focus_index]
|
|
133
|
+
end
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
# Indices of focusable fields, memoized.
|
|
136
|
+
def focusable_indices
|
|
137
|
+
@focusable_indices ||= fields.each_index.select { |index| fields[index].focusable? }
|
|
138
|
+
end
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
# The first index of a focusable field, or nil when no fields are focusable.
|
|
141
|
+
def first_focusable_index
|
|
142
|
+
fields.each_index.find { |index| fields[index].focusable? }
|
|
143
|
+
end
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
# On initialization, ensures :focus_index points at a focusable field.
|
|
146
|
+
def clamp_focus
|
|
147
|
+
return if focusable_indices.empty?
|
|
148
|
+
return if focusable_indices.include?(state[:focus_index])
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
end
|
|
150
|
+
state[:focus_index] = focusable_indices.first
|
|
153
151
|
end
|
|
154
152
|
end
|
|
155
153
|
end
|
|
@@ -1,56 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Charming
|
|
4
|
-
module
|
|
5
|
-
module
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
4
|
+
module Components
|
|
5
|
+
# KeyboardHandler is a mixin module that provides keyboard event dispatch by mapping symbolic key names
|
|
6
|
+
# to private method calls. Implementors must define a constant +KEY_ACTIONS+ as a hash where each key is
|
|
7
|
+
# a symbol (e.g., :up, :down, :enter) and each value is the target method name (e.g., :move_up). Call
|
|
8
|
+
# +handle_key(event)+ with any event object; it uses Charming.key_of to resolve the raw event to a symbol,
|
|
9
|
+
# looks up the corresponding action in KEY_ACTIONS, sends that method on self, and returns :handled if an
|
|
10
|
+
# action was found. Returns nil (via :handled being truthy or not) when no matching key exists.
|
|
11
|
+
module KeyboardHandler
|
|
12
|
+
VIM_KEYMAP = {
|
|
13
|
+
up: :k,
|
|
14
|
+
down: :j,
|
|
15
|
+
left: :h,
|
|
16
|
+
right: :l
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def handle_key(event)
|
|
20
|
+
key = Charming.key_of(event)
|
|
21
|
+
action = key_actions[key]
|
|
22
|
+
return unless action
|
|
23
|
+
|
|
24
|
+
send(action)
|
|
25
|
+
:handled
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
private
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
def key_actions
|
|
31
|
+
base_key_actions.merge(normalized_keymap)
|
|
32
|
+
end
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
def base_key_actions
|
|
35
|
+
self.class.const_get(:KEY_ACTIONS)
|
|
36
|
+
end
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
def normalized_keymap
|
|
39
|
+
resolved_keymap.each_with_object({}) do |(action_key, keys), actions|
|
|
40
|
+
action = base_key_actions[action_key.to_sym]
|
|
41
|
+
next unless action
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
end
|
|
43
|
+
Array(keys).each { |key| actions[key.to_sym] = action }
|
|
46
44
|
end
|
|
45
|
+
end
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
47
|
+
def resolved_keymap
|
|
48
|
+
case @keymap
|
|
49
|
+
when :vim then VIM_KEYMAP
|
|
50
|
+
when nil then {}
|
|
51
|
+
else @keymap
|
|
54
52
|
end
|
|
55
53
|
end
|
|
56
54
|
end
|