///|
/// Context hierarchy and platform overlap helpers.
fn context_index(keymap : Keymap, name : String) -> Int? {
for i, context in keymap.contexts {
if context.name == name {
return Some(i)
}
}
None
}
///|
fn context_by_name(keymap : Keymap, name : String) -> Context? {
match context_index(keymap, name) {
Some(index) => Some(keymap.contexts[index])
None => None
}
}
///|
fn context_exists(keymap : Keymap, name : String) -> Bool {
context_index(keymap, name) is Some(_)
}
///|
fn context_is_ancestor(
keymap : Keymap,
ancestor : String,
child : String,
) -> Bool {
if ancestor == child {
return true
}
let mut current = child
let mut steps = 0
while steps <= keymap.contexts.length() {
match context_by_name(keymap, current) {
Some(context) => {
if context.parent.length() == 0 {
return false
}
if context.parent == ancestor {
return true
}
current = context.parent
}
None => return false
}
steps += 1
}
false
}
///|
/// Two contexts overlap when one is active inside the other. Sibling editor
/// modes intentionally do not conflict because only one can be active.
fn contexts_overlap(keymap : Keymap, left : String, right : String) -> Bool {
context_is_ancestor(keymap, left, right) ||
context_is_ancestor(keymap, right, left)
}
///|
/// A binding is active when its declared context is an ancestor of the
/// currently active context. This is directional: a child binding must not
/// leak into the global context merely because the two scopes overlap.
fn binding_active_in_context(
keymap : Keymap,
binding_context : String,
active_context : String,
) -> Bool {
context_is_ancestor(keymap, binding_context, active_context)
}
///|
fn platform_parts(platform : String) -> Array[String] {
platform
.replace(old="|", new=",")
.split(",")
.map(x => lower_ascii(trim_ascii(x.to_owned())))
.filter(x => x.length() > 0)
.to_array()
}
///|
fn platform_contains(platform : String, wanted : String) -> Bool {
let values = platform_parts(platform)
for value in values {
if value == wanted || value == "all" {
return true
}
}
false
}
///|
/// Platform names are case-insensitive and can be combined with `|` or `,`.
pub fn platforms_overlap(left : String, right : String) -> Bool {
let l = platform_parts(left)
let r = platform_parts(right)
if l.length() == 0 || r.length() == 0 {
return true
}
for value in l {
if platform_contains(right, value) {
return true
}
}
for value in r {
if platform_contains(left, value) {
return true
}
}
false
}
///|
fn platform_label(platform : String) -> String {
let clean = lower_ascii(trim_ascii(platform))
if clean.length() == 0 {
"all"
} else {
clean
}
}
///|
fn reserved_matches(keymap : Keymap, binding : Binding) -> Bool {
let canonical = binding.keys.canonical
let platform = platform_label(binding.platform)
for marker in keymap.reserved {
let (marker_platform, marker_key) = split_once(marker, ":")
if marker_key == canonical &&
(
marker_platform == "all" ||
platform_contains(platform, marker_platform) ||
platform_contains(marker_platform, platform)
) {
return true
}
}
false
}
///|
fn binding_context_rank(keymap : Keymap, binding : Binding) -> Int {
match context_by_name(keymap, binding.context) {
Some(context) => context.rank
None => -1000
}
}
///|
fn more_specific(keymap : Keymap, left : Binding, right : Binding) -> Binding {
let left_rank = binding_context_rank(keymap, left)
let right_rank = binding_context_rank(keymap, right)
if left.priority > right.priority ||
(left.priority == right.priority && left_rank >= right_rank) {
left
} else {
right
}
}
///|
fn context_chain(keymap : Keymap, name : String) -> Array[String] {
let result : Array[String] = []
let mut current = name
let mut steps = 0
while current.length() > 0 && steps <= keymap.contexts.length() {
result.push(current)
match context_by_name(keymap, current) {
Some(context) => current = context.parent
None => current = ""
}
steps += 1
}
result
}
///|
/// Explain why two bindings are considered to overlap.
pub fn overlap_explanation(
keymap : Keymap,
left : Binding,
right : Binding,
) -> String {
let contexts = if left.context == right.context {
"same context '" + left.context + "'"
} else {
"context chains " +
context_chain(keymap, left.context).join(" > ") +
" and " +
context_chain(keymap, right.context).join(" > ")
}
let platforms = if lower_ascii(left.platform) == lower_ascii(right.platform) {
"same platform " + platform_label(left.platform)
} else {
"platforms " +
platform_label(left.platform) +
" and " +
platform_label(right.platform) +
" overlap"
}
contexts + "; " + platforms
}