First draft of Cursor Position hint on Windows

This commit is contained in:
Federico Terzi 2019-10-22 23:03:58 +02:00
parent 5f71d0ad24
commit 9bbdb8d95f
7 changed files with 125 additions and 19 deletions

View File

@ -453,24 +453,7 @@ void send_string(const wchar_t * string) {
* Send the backspace keypress, *count* times.
*/
void delete_string(int32_t count) {
std::vector<INPUT> vec;
for (int i = 0; i < count; i++) {
INPUT input = { 0 };
input.type = INPUT_KEYBOARD;
input.ki.wScan = 0;
input.ki.time = 0;
input.ki.dwExtraInfo = 0;
input.ki.wVk = VK_BACK;
input.ki.dwFlags = 0; // 0 for key press
vec.push_back(input);
input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
vec.push_back(input);
}
SendInput(vec.size(), vec.data(), sizeof(INPUT));
send_multi_vkey(VK_BACK, count);
}
void send_vkey(int32_t vk) {
@ -492,6 +475,27 @@ void send_vkey(int32_t vk) {
SendInput(vec.size(), vec.data(), sizeof(INPUT));
}
void send_multi_vkey(int32_t vk, int32_t count) {
std::vector<INPUT> vec;
for (int i = 0; i < count; i++) {
INPUT input = { 0 };
input.type = INPUT_KEYBOARD;
input.ki.wScan = 0;
input.ki.time = 0;
input.ki.dwExtraInfo = 0;
input.ki.wVk = vk;
input.ki.dwFlags = 0; // 0 for key press
vec.push_back(input);
input.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
vec.push_back(input);
}
SendInput(vec.size(), vec.data(), sizeof(INPUT));
}
void trigger_paste() {
std::vector<INPUT> vec;

View File

@ -64,6 +64,11 @@ extern "C" void send_string(const wchar_t * string);
*/
extern "C" void send_vkey(int32_t vk);
/*
* Send the given Virtual Key press multiple times
*/
extern "C" void send_multi_vkey(int32_t vk, int32_t count);
/*
* Send the backspace keypress, *count* times.
*/

View File

@ -55,6 +55,7 @@ extern {
pub fn eventloop();
pub fn send_string(string: *const u16);
pub fn send_vkey(vk: i32);
pub fn send_multi_vkey(vk: i32, count: i32);
pub fn delete_string(count: i32);
pub fn trigger_paste();
}

View File

@ -185,6 +185,13 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
self.keyboard_manager.trigger_paste();
},
}
// Cursor Hint
if let Some(cursor_rewind) = m._cursor_rewind {
// Simulate left arrow key presses to bring the cursor into the desired position
self.keyboard_manager.move_cursor_left(cursor_rewind);
}
}
fn on_enable_update(&self, status: bool) {

View File

@ -31,6 +31,7 @@ pub trait KeyboardManager {
fn send_enter(&self);
fn trigger_paste(&self);
fn delete_string(&self, count: i32);
fn move_cursor_left(&self, count: i32);
}
// WINDOWS IMPLEMENTATION

View File

@ -55,4 +55,11 @@ impl super::KeyboardManager for WindowsKeyboardManager {
delete_string(count)
}
}
fn move_cursor_left(&self, count: i32) {
unsafe {
// Send the left arrow key multiple times
send_multi_vkey(0x25, count)
}
}
}

View File

@ -38,6 +38,11 @@ pub struct Match {
// Automatically calculated from the trigger, used by the matcher to check for correspondences.
#[serde(skip_serializing)]
pub _trigger_sequence: Vec<TriggerEntry>,
// If a cursor position hint is present, this value contains the amount of "left" moves necessary
// to arrive to the target point
#[serde(skip_serializing)]
pub _cursor_rewind: Option<i32>,
}
impl <'de> serde::Deserialize<'de> for Match {
@ -55,6 +60,33 @@ impl<'a> From<&'a AutoMatch> for Match{
static ref VAR_REGEX: Regex = Regex::new("\\{\\{\\s*(\\w+)\\s*\\}\\}").unwrap();
}
// TODO: may need to replace windows newline (\r\n) with newline only (\n)
let mut new_replace = other.replace.clone();
// Check if the replace result contains a Cursor Hint
let cursor_rewind = if other.replace.contains("{{|}}") {
let index = other.replace.find("{{|}}");
if let Some(index) = index {
// Convert the byte index to a char index
let char_str = &other.replace[0..index];
let char_index = char_str.chars().count();
let total_size = other.replace.chars().count();
// Remove the {{|}} placeholder
new_replace = other.replace.replace("{{|}}", "");
// Calculate the amount of rewind moves needed (LEFT ARROW).
// Subtract also 5, equal to the number of chars of the placeholder "{{|}}"
let moves = (total_size - char_index - 5) as i32;
Some(moves)
}else{
None
}
}else{
None
};
// Check if the match contains variables
let has_vars = VAR_REGEX.is_match(&other.replace);
@ -70,11 +102,12 @@ impl<'a> From<&'a AutoMatch> for Match{
Self {
trigger: other.trigger.clone(),
replace: other.replace.clone(),
replace: new_replace,
vars: other.vars.clone(),
word: other.word.clone(),
_has_vars: has_vars,
_trigger_sequence: trigger_sequence,
_cursor_rewind: cursor_rewind,
}
}
}
@ -208,4 +241,52 @@ mod tests {
assert_eq!(_match._trigger_sequence[3], TriggerEntry::Char('t'));
assert_eq!(_match._trigger_sequence[4], TriggerEntry::WordSeparator);
}
#[test]
fn test_match_cursor_hint_not_present() {
let match_str = r###"
trigger: "test"
replace: "This is a test"
"###;
let _match : Match = serde_yaml::from_str(match_str).unwrap();
assert!(_match._cursor_rewind.is_none());
}
#[test]
fn test_match_cursor_hint_should_be_removed() {
let match_str = r###"
trigger: "test"
replace: "Testing the {{|}} cursor position"
"###;
let _match : Match = serde_yaml::from_str(match_str).unwrap();
assert_eq!(_match.replace, "Testing the cursor position");
}
#[test]
fn test_match_cursor_rewind_single_line() {
let match_str = r###"
trigger: "test"
replace: "Testing the {{|}} cursor position"
"###;
let _match : Match = serde_yaml::from_str(match_str).unwrap();
assert_eq!(_match._cursor_rewind.unwrap(), 16);
}
#[test]
fn test_match_cursor_rewind_multiple_line() {
let match_str = r###"
trigger: "test"
replace: "Testing the \n{{|}}\n cursor position"
"###;
let _match : Match = serde_yaml::from_str(match_str).unwrap();
assert_eq!(_match._cursor_rewind.unwrap(), 17);
}
}