neo-layout/windows/neo-vars/src/compose.ahk

146 lines
5.1 KiB
AutoHotkey

; -*- encoding:utf-8 -*-
;
; Global compose definitions
;
; Compose-definitions root object:
; Hosts compose-sequences in a nested structure of associative arrays
; ("compose-planes").
; A child-plane listed under a key "x" corresponds to the character "x"
; to be appended to the compose-sequence (where "x" is a keyId).
; The key that is the special character 'COMP_BACK_CHAR' links back to
; the parent plane. Special keys such as '_result' or '_fallback' hold
; compose-result and fallback characters; a plane with '_result' is
; assumed to be a leaf without child-planes.
composeDict := {} ; first compose-plane
; Reverse-lookup associative array, synchronized to composeDict:
; composeSequences[resultChars] = array of all leaf-planes whose '_result'
; is equal to 'resultChars'.
composeSequences := {}
; Read compose-definitions from XCompose-files ("dynamic compose")
; Appends sequences in provided order to global compose definitions
LoadCurrentCompose() {
global ini
; Read compose-input files from ini-file
IniRead, dynComposeFilesPrefix, %ini%, Global, dynComposeFilesPrefix , ""
IniRead, dynComposeFiles, %ini%, Global, dynComposeFiles , ""
; Backwards-compatible defaults
if (not dynComposeFiles) {
dynComposeFilesPrefix := "..\..\..\Compose\src\"
dynComposeFiles := "base.module, en_US.UTF-8, greek.module, math.module, cyrillic.module, lang.module"
}
; Parse and load compose sequences:
keySym2KeyIdMap := makeKeySym2KeyIdMap()
Loop, parse, dynComposeFiles, CSV, %A_Space%%A_Tab%
{
f := dynComposeFilesPrefix . A_LoopField
if FileExist(f)
parseComposeFile(f, keySym2KeyIdMap, "makeCompose")
}
}
; Add a new compose-sequence to global compose-definitions
;
; Arguments:
;
; sequence (in)
; Array of one or more single characters or neovars keyIds
; (U-code or special key name). If empty, False is returned.
; resultChars (in)
; One or more characters that shall be sent in place of the sequence,
; encoded in a consecutive string of keyIds (e.g. "UxxxxxxUyyyyyyPzzzzzz..").
; If empty, no compose-definition will be created, all conflicts
; with existing sequences will be ignored, and False is returned.
; fallback:="" (in)
; If a non-empty string is provided, it must be a consecutive string
; of U-codes, and becomes the fallback characters sent out when a
; compose-miss on the last character in the provided sequence occurs.
; If an empty string is provided (default), fallback is not enabled.
; Any existing (non-empty) fallback can not be overwritten, and False
; is returned in any attempt to do so (ignoring the provided sequence).
;
; Returns:
; True on success, False otherwise.
;
; Notes:
;
; Updates global dictionaries 'composeDict' and 'composeSequences'
; On entry, 'composeDict' may hold previously defined compose-sequences.
; On successful exit, the provided sequence is added to composeDict
; without overwriting or making inaccessible any existing sequences.
; Similarly, 'composeSequences' is updated with 'sequence' on successful exit.
;
; If the sequence contains any COMP_BACK_CHAR (backspaces by default)
; following any othe character, the COMP_BACK_CHAR acts as "compose-undo"
; and effectively remove the preceding character from the compose sequence
; (if no more such characters exist, the COMP_BACK_CHAR acts litterally)
;
; If the same, a longer, or a shorter sequence already exists, the existing
; sequence will not be overwritten, and False is returned instead.
;
makeCompose(sequence, resultChars, fallback:="") {
global composeDict, composeSequences, COMP_BACK_CHAR
; Quick return: empty result
; (not distinguishable from compose-fail)
if (resultChars == "")
return False
; Quick return: empty sequence
if (sequence.Length() == 0)
return False
; Set up sub-compose planes along the sequence
nextSubDict := composeDict ; current compose-plane
depth := 0
While (depth < sequence.Length())
{
; Enter next compose-plane
subDict := nextSubDict
char := util_char2Ucode(sequence[++depth])
nextSubDict := subDict[char]
if (not nextSubDict) {
; plane does not exist, create it
nextSubDict := {(COMP_BACK_CHAR):subDict, _sequenceChar: char}
subDict[char] := nextSubDict
} else if (nextSubDict._result) {
; error: shorter or equal sequence exists already
return False
} else if (depth == sequence.Length()) {
; error: longer sequence exists already
return False
}
}
; Enable fallback on compose-miss
if (fallback != "") {
if (subDict._fallback)
; error: fallback is already defined
return False
else
subDict._fallback := fallback
}
; Resolve sequence in last plane
nextSubDict._result := resultChars
; Update reverse lookup
; (map result to last plane; sequences can then be rebuild backwards
; from _sequenceChar's, going back step by step through COMP_BACK_CHAR)
compSeqList := composeSequences[resultChars]
if (not compSeqList) {
compSeqList := []
composeSequences[resultChars] := compSeqList
}
compSeqList.push(nextSubDict)
; Successfully updated compose definitions
return True
}