173 lines
6.3 KiB
Python
173 lines
6.3 KiB
Python
"""
|
|
Command processor for Ubuntu Voice Typing.
|
|
|
|
This module processes text commands from speech recognition, such as
|
|
"new line", "period", etc.
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CommandProcessor:
|
|
"""
|
|
Processes text commands in speech recognition results.
|
|
|
|
This class handles special commands like "new line", "period",
|
|
"delete that", etc.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize the command processor."""
|
|
# Map of command phrases to their actions
|
|
self.text_commands = {
|
|
# Line commands
|
|
"new line": "\n",
|
|
"new paragraph": "\n\n",
|
|
|
|
# Punctuation
|
|
"period": ".",
|
|
"full stop": ".",
|
|
"comma": ",",
|
|
"question mark": "?",
|
|
"exclamation mark": "!",
|
|
"exclamation point": "!",
|
|
"semicolon": ";",
|
|
"colon": ":",
|
|
"dash": "-",
|
|
"hyphen": "-",
|
|
"underscore": "_",
|
|
"quote": "\"",
|
|
"single quote": "'",
|
|
"open parenthesis": "(",
|
|
"close parenthesis": ")",
|
|
"open bracket": "[",
|
|
"close bracket": "]",
|
|
"open brace": "{",
|
|
"close brace": "}",
|
|
}
|
|
|
|
# Special action commands that don't directly map to text
|
|
self.action_commands = {
|
|
"delete that": "delete_last",
|
|
"scratch that": "delete_last",
|
|
"undo": "undo",
|
|
"redo": "redo",
|
|
"select all": "select_all",
|
|
"select line": "select_line",
|
|
"select word": "select_word",
|
|
"select paragraph": "select_paragraph",
|
|
"cut": "cut",
|
|
"copy": "copy",
|
|
"paste": "paste",
|
|
}
|
|
|
|
# Formatting commands that modify the next word
|
|
self.format_commands = {
|
|
"capitalize": "capitalize_next",
|
|
"uppercase": "capitalize_next",
|
|
"all caps": "uppercase_next",
|
|
"lowercase": "lowercase_next",
|
|
"no spaces": "no_spaces_next",
|
|
}
|
|
|
|
# Active format modifiers
|
|
self.active_formats = set()
|
|
|
|
# Compile regex patterns for faster matching
|
|
self._compile_patterns()
|
|
|
|
def _compile_patterns(self):
|
|
"""Compile regex patterns for command matching."""
|
|
# Create regex pattern for text commands
|
|
text_cmd_pattern = r'\b(' + '|'.join(re.escape(cmd) for cmd in self.text_commands.keys()) + r')\b'
|
|
self.text_cmd_regex = re.compile(text_cmd_pattern, re.IGNORECASE)
|
|
|
|
# Create regex pattern for action commands
|
|
action_cmd_pattern = r'\b(' + '|'.join(re.escape(cmd) for cmd in self.action_commands.keys()) + r')\b'
|
|
self.action_cmd_regex = re.compile(action_cmd_pattern, re.IGNORECASE)
|
|
|
|
# Create regex pattern for format commands
|
|
format_cmd_pattern = r'\b(' + '|'.join(re.escape(cmd) for cmd in self.format_commands.keys()) + r')\b'
|
|
self.format_cmd_regex = re.compile(format_cmd_pattern, re.IGNORECASE)
|
|
|
|
def process_text(self, text: str) -> Tuple[str, List[str]]:
|
|
"""
|
|
Process text commands in the recognized text.
|
|
|
|
Args:
|
|
text: The recognized text to process
|
|
|
|
Returns:
|
|
Tuple of (processed_text, actions)
|
|
- processed_text: The text with commands replaced
|
|
- actions: List of special actions to perform
|
|
"""
|
|
if not text:
|
|
return "", []
|
|
|
|
logger.debug(f"Processing commands in text: {text}")
|
|
|
|
# Convert to lowercase for easier matching
|
|
lower_text = text.lower()
|
|
processed_text = text
|
|
actions = []
|
|
|
|
# Process action commands first (delete that, undo, etc.)
|
|
action_matches = self.action_cmd_regex.findall(lower_text)
|
|
for match in action_matches:
|
|
action = self.action_commands[match]
|
|
actions.append(action)
|
|
# Remove the command from the text
|
|
processed_text = re.sub(r'\b' + re.escape(match) + r'\b', '', processed_text, flags=re.IGNORECASE)
|
|
|
|
# Process format commands
|
|
format_matches = self.format_cmd_regex.findall(lower_text)
|
|
for match in format_matches:
|
|
action = self.format_commands[match]
|
|
self.active_formats.add(action)
|
|
# Remove the command from the text
|
|
processed_text = re.sub(r'\b' + re.escape(match) + r'\b', '', processed_text, flags=re.IGNORECASE)
|
|
|
|
# Apply active format modifiers to the next word
|
|
if self.active_formats:
|
|
# Find the next word
|
|
word_match = re.search(r'\b(\w+)\b', processed_text)
|
|
if word_match:
|
|
word = word_match.group(1)
|
|
start, end = word_match.span(1)
|
|
|
|
# Apply formatting
|
|
formatted_word = word
|
|
for format_type in self.active_formats:
|
|
if format_type == "capitalize_next":
|
|
formatted_word = formatted_word.capitalize()
|
|
elif format_type == "uppercase_next":
|
|
formatted_word = formatted_word.upper()
|
|
elif format_type == "lowercase_next":
|
|
formatted_word = formatted_word.lower()
|
|
elif format_type == "no_spaces_next":
|
|
# This will be applied when combining with the next word
|
|
pass
|
|
|
|
# Replace the word in the text
|
|
processed_text = processed_text[:start] + formatted_word + processed_text[end:]
|
|
|
|
# Clear active formats
|
|
self.active_formats.clear()
|
|
|
|
# Finally, process text commands (period, comma, etc.)
|
|
def replace_command(match):
|
|
cmd = match.group(0).lower()
|
|
replacement = self.text_commands.get(cmd, "")
|
|
return replacement
|
|
|
|
processed_text = self.text_cmd_regex.sub(replace_command, processed_text)
|
|
|
|
# Clean up multiple spaces
|
|
processed_text = re.sub(r'\s+', ' ', processed_text).strip()
|
|
|
|
return processed_text, actions |