Skip to main content

Mountain/Environment/
KeybindingProvider.rs

1//! # KeybindingProvider (Environment)
2//!
3//! Implements the `KeybindingProvider` trait for `MountainEnvironment`,
4//! providing keybinding resolution, conflict detection, and command activation
5//! based on keyboard input.
6//!
7//! Keybindings are collected from three sources in descending priority:
8//! user `keybindings.json` overrides, extension `contributes.keybindings`,
9//! and Mountain built-ins. Negative commands (prefixed with `-`) act as
10//! unbind rules and remove the matching entry.
11//!
12//! ## When clause evaluation
13//!
14//! "When" clauses are boolean expressions over context keys that control
15//! whether a keybinding is active. Examples:
16//! - `"editorTextFocus && !inQuickOpen"` - only when editor has focus
17//! - `"debugState != 'inactive'"` - only when debugging
18//! - `"resourceLangId == python"` - only for Python files
19//!
20//! Current implementation stores when clauses but only partially evaluates
21//! them. Full expression parsing and evaluation is pending.
22//!
23//! ## VS Code reference
24//!
25//! - `vs/platform/keybinding/common/keybinding.ts`
26//! - `vs/platform/keybinding/common/keybindingResolver.ts`
27//! - `vs/platform/keybinding/common/keybindingsRegistry.ts`
28//! - `vs/platform/contextkey/common/contextkey.ts`
29
30use std::{collections::HashMap, sync::Arc};
31
32use CommonLibrary::{
33	Effect::ApplicationRunTime::ApplicationRunTime as _,
34	Error::CommonError::CommonError,
35	FileSystem::ReadFile::ReadFile,
36	Keybinding::KeybindingProvider::KeybindingProvider,
37};
38use async_trait::async_trait;
39use serde_json::{Value, json};
40use tauri::Manager;
41
42use super::{MountainEnvironment::MountainEnvironment, Utility};
43use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
44
45// TODO: full "when" clause expression parser/evaluator, precedence scoring
46// algorithm, chord support ("Ctrl+K Ctrl+C"), platform modifier conversion
47// (Cmd/Ctrl/Alt), conflict detection/warnings, localization, custom schemes
48// (vim/emacs/sublime), keybinding recording, per-profile keybindings,
49// export/import, search/discovery UI, telemetry.
50#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
51#[serde(rename_all = "camelCase")]
52struct KeybindingRule {
53	key:String,
54
55	command:String,
56
57	when:Option<String>,
58
59	args:Option<Value>,
60}
61
62#[async_trait]
63impl KeybindingProvider for MountainEnvironment {
64	async fn GetResolvedKeybinding(&self) -> Result<Value, CommonError> {
65		dev_log!("keybinding", "[KeybindingProvider] Resolving all keybindings...");
66
67		let mut ResolvedKeybindings:HashMap<String, KeybindingRule> = HashMap::new();
68
69		// 1. Collect default keybindings from extensions
70		let Extensions = self
71			.ApplicationState
72			.Extension
73			.ScannedExtensions
74			.ScannedExtensions
75			.lock()
76			.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
77			.clone();
78
79		for Extension in Extensions.values() {
80			if let Some(Contributes) = Extension.Contributes.as_ref().and_then(|c| c.get("keybindings")) {
81				if let Some(KeybindingsArray) = Contributes.as_array() {
82					for KeybindingValue in KeybindingsArray {
83						if let Ok(KeybindingRule) = serde_json::from_value::<KeybindingRule>(KeybindingValue.clone()) {
84							let UniqueKey =
85								format!("{}{}", KeybindingRule.key, KeybindingRule.when.as_deref().unwrap_or(""));
86
87							ResolvedKeybindings.insert(UniqueKey, KeybindingRule);
88						}
89					}
90				}
91			}
92		}
93
94		// 2. Load and apply user-defined keybindings from keybindings.json
95		let UserKeybindingsPath = self
96			.ApplicationHandle
97			.path()
98			.app_config_dir()
99			.map_err(|Error| {
100				CommonError::ConfigurationLoad { Description:format!("Cannot find app config dir: {}", Error) }
101			})?
102			.join("keybindings.json");
103
104		let RunTime = self.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
105
106		if let Ok(Content) = RunTime.Run(ReadFile(UserKeybindingsPath)).await {
107			if let Ok(UserKeybindings) = serde_json::from_slice::<Vec<KeybindingRule>>(&Content) {
108				for UserKeybinding in UserKeybindings {
109					let UniqueKey = format!("{}{}", UserKeybinding.key, UserKeybinding.when.as_deref().unwrap_or(""));
110
111					if UserKeybinding.command.starts_with('-') {
112						// Unbind rule
113						ResolvedKeybindings.remove(&UniqueKey);
114					} else {
115						// Override rule
116						ResolvedKeybindings.insert(UniqueKey, UserKeybinding);
117					}
118				}
119			} else {
120				dev_log!(
121					"keybinding",
122					"warn: [KeybindingProvider] Failed to parse user keybindings.json. It may be malformed."
123				);
124			}
125		}
126
127		let FinalRules:Vec<KeybindingRule> = ResolvedKeybindings.into_values().collect();
128
129		Ok(json!(FinalRules))
130	}
131}