diff --git a/bamboo.go b/bamboo.go index 8d2b77f..fbe07cc 100644 --- a/bamboo.go +++ b/bamboo.go @@ -33,15 +33,22 @@ const ( W2uEverywhere = 2 ) +const ( + BracketTransformFollowFlags = -1 + BracketTransformDisabled = 0 + BracketTransformNonStart = 1 + BracketTransformEverywhere = 2 +) + const ( EfreeToneMarking uint = 1 << iota EstdToneStyle EautoCorrectEnabled Ew2uEnabled + EbracketTransformEnabled EstdFlags = EfreeToneMarking | EstdToneStyle | EautoCorrectEnabled | Ew2uEnabled ) - type Transformation struct { Rule Rule Target *Transformation @@ -53,6 +60,7 @@ type IEngine interface { GetInputMethod() InputMethod ProcessKey(rune, Mode) SetW2UMode(int) + SetBracketTransformMode(int) ProcessString(string, Mode) GetProcessedString(Mode) string IsValid(bool) bool @@ -64,17 +72,19 @@ type IEngine interface { } type BambooEngine struct { - composition []*Transformation - inputMethod InputMethod - flags uint - w2uMode int // W2uFollowFlags, W2uDisabled, W2uNonStart, W2uEverywhere + composition []*Transformation + inputMethod InputMethod + flags uint + w2uMode int // W2uFollowFlags, W2uDisabled, W2uNonStart, W2uEverywhere + bracketTransformMode int // BracketTransformFollowFlags, Disabled, NonStart, Everywhere } func NewEngine(inputMethod InputMethod, flag uint) IEngine { engine := BambooEngine{ - inputMethod: inputMethod, - flags: flag, - w2uMode: W2uFollowFlags, + inputMethod: inputMethod, + flags: flag, + w2uMode: W2uFollowFlags, + bracketTransformMode: BracketTransformFollowFlags, } return &engine } @@ -93,28 +103,52 @@ func (e *BambooEngine) SetW2UMode(mode int) { } } +func (e *BambooEngine) SetBracketTransformMode(mode int) { + if mode >= BracketTransformFollowFlags && mode <= BracketTransformEverywhere { + e.bracketTransformMode = mode + } +} + func (e *BambooEngine) GetFlag(flag uint) uint { return e.flags } func (e *BambooEngine) IsValid(inputIsFullComplete bool) bool { - var _, last = extractLastWord(e.composition, e.GetInputMethod().Keys) + var keys []rune + if e.isBracketEnabled() { + keys = append(e.GetInputMethod().Keys, '[', ']', '{', '}') + } else { + keys = e.GetInputMethod().Keys + } + var _, last = extractLastWord(e.composition, keys) return isValid(last, inputIsFullComplete) } func (e *BambooEngine) GetProcessedString(mode Mode) string { var tmp []*Transformation + var keys []rune + if e.isBracketEnabled() { + keys = append(e.inputMethod.Keys, '[', ']', '{', '}') + } else { + keys = e.inputMethod.Keys + } if mode&FullText != 0 { tmp = e.composition } else if mode&PunctuationMode != 0 { - _, tmp = extractLastWordWithPunctuationMarks(e.composition, e.inputMethod.Keys) + _, tmp = extractLastWordWithPunctuationMarks(e.composition, keys) return Flatten(tmp, VietnameseMode) } else { - _, tmp = extractLastWord(e.composition, e.inputMethod.Keys) + _, tmp = extractLastWord(e.composition, keys) } return Flatten(tmp, mode) } +func (e *BambooEngine) isBracketEnabled() bool { + return (e.bracketTransformMode == BracketTransformEverywhere) || + (e.bracketTransformMode == BracketTransformNonStart && len(e.composition) > 0) || + (e.bracketTransformMode == BracketTransformFollowFlags && e.flags&EbracketTransformEnabled != 0) +} + func (e *BambooEngine) getApplicableRules(key rune) []Rule { var applicableRules []Rule for _, inputRule := range e.inputMethod.Rules { @@ -130,7 +164,16 @@ func (e *BambooEngine) findTargetByKey(composition []*Transformation, key rune) } func (e *BambooEngine) CanProcessKey(key rune) bool { - return canProcessKey(key, e.inputMethod.Keys) + if canProcessKey(key, e.inputMethod.Keys) { + return true + } + if e.isBracketEnabled() { + lowerKey := unicode.ToLower(key) + if lowerKey == '[' || lowerKey == ']' || lowerKey == '{' || lowerKey == '}' { + return true + } + } + return false } func (e *BambooEngine) generateTransformations(composition []*Transformation, lowerKey rune, isUpperCase bool) []*Transformation { @@ -139,29 +182,68 @@ func (e *BambooEngine) generateTransformations(composition []*Transformation, lo // If none of the applicable_rules can actually be applied then this new // transformation fall-backs to an APPENDING one. transformations = generateFallbackTransformations(composition, e.getApplicableRules(lowerKey), lowerKey, isUpperCase) - - // Unified Modular W2U Logic - canApplyW2U := (e.w2uMode == W2uEverywhere) || - (e.w2uMode == W2uNonStart && len(composition) > 0) || - (e.w2uMode == W2uFollowFlags && e.flags&Ew2uEnabled != 0) - - if canApplyW2U && lowerKey == 'w' && len(transformations) > 0 { - if transformations[0].Rule.Result == 'w' { - transformations[0].Rule.Result = 'ư' - transformations[0].Rule.EffectOn = 'ư' - } else if transformations[0].Rule.Result == 'W' { - transformations[0].Rule.Result = 'Ư' - transformations[0].Rule.EffectOn = 'Ư' + } + + if e.isBracketEnabled() { + if len(composition) > 0 { + lastTrans := composition[len(composition)-1] + if (lowerKey == '[' || lowerKey == '{') && (lastTrans.Rule.Key == '[' || lastTrans.Rule.Key == '{') && (lastTrans.Rule.Result == 'ơ' || lastTrans.Rule.Result == 'Ơ') { + return []*Transformation{{ + Target: lastTrans, + Rule: Rule{ + EffectType: MarkTransformation, + Effect: uint8(MarkRaw), + }, + }} + } + if (lowerKey == ']' || lowerKey == '}') && (lastTrans.Rule.Key == ']' || lastTrans.Rule.Key == '}') && (lastTrans.Rule.Result == 'ư' || lastTrans.Rule.Result == 'Ư') { + return []*Transformation{{ + Target: lastTrans, + Rule: Rule{ + EffectType: MarkTransformation, + Effect: uint8(MarkRaw), + }, + }} + } + } + if (lowerKey == '[' || lowerKey == '{') && len(transformations) > 0 && (transformations[0].Rule.Result == '[' || transformations[0].Rule.Result == '{') { + transformations[0].Rule.Result = 'ơ' + transformations[0].Rule.EffectOn = 'ơ' + if lowerKey == '{' { + transformations[0].IsUpperCase = true } } - var newComposition = append(composition, transformations...) + if (lowerKey == ']' || lowerKey == '}') && len(transformations) > 0 && (transformations[0].Rule.Result == ']' || transformations[0].Rule.Result == '}') { + transformations[0].Rule.Result = 'ư' + transformations[0].Rule.EffectOn = 'ư' + if lowerKey == '}' { + transformations[0].IsUpperCase = true + } + } + } - // Implement the uwo+ typing shortcut by creating a virtual - // Mark.HORN rule that targets 'u' or 'o'. - if virtualTrans := e.applyUowShortcut(newComposition); virtualTrans != nil { - transformations = append(transformations, virtualTrans) + // Keep old W2U logic for backward compatibility + canApplyW2U := (e.w2uMode == W2uEverywhere) || + (e.w2uMode == W2uNonStart && len(composition) > 0) || + (e.w2uMode == W2uFollowFlags && e.flags&Ew2uEnabled != 0) + + if canApplyW2U && lowerKey == 'w' && len(transformations) > 0 { + switch transformations[0].Rule.Result { + case 'w': + transformations[0].Rule.Result = 'ư' + transformations[0].Rule.EffectOn = 'ư' + case 'W': + transformations[0].Rule.Result = 'Ư' + transformations[0].Rule.EffectOn = 'Ư' } } + var newComposition = append(composition, transformations...) + + // Implement the uwo+ typing shortcut by creating a virtual + // Mark.HORN rule that targets 'u' or 'o'. + if virtualTrans := e.applyUowShortcut(newComposition); virtualTrans != nil { + transformations = append(transformations, virtualTrans) + } /** * Sometimes, a tone's position in a previous state must be changed to fit the new state * @@ -172,7 +254,6 @@ func (e *BambooEngine) generateTransformations(composition []*Transformation, lo transformations = append(transformations, e.refreshLastToneTarget(append(composition, transformations...))...) return transformations } - func (e *BambooEngine) newComposition(composition []*Transformation, key rune, isUpperCase bool) []*Transformation { // Just process the key stroke on the last syllable var previousTransformations, lastSyllable = extractLastSyllable(composition) diff --git a/bamboo_test.go b/bamboo_test.go index 2e5428e..97c6869 100644 --- a/bamboo_test.go +++ b/bamboo_test.go @@ -13,8 +13,10 @@ import ( ) func newStdEngine() IEngine { - var im = ParseInputMethod(InputMethodDefinitions, "Telex 2") - return NewEngine(im, EstdFlags) + var im = ParseInputMethod(InputMethodDefinitions, "Telex") + var ng = NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformEverywhere) + return ng } func TestProcessString(t *testing.T) { @@ -171,16 +173,17 @@ func TestTelex23(t *testing.T) { if ng.GetProcessedString(VietnameseMode) != "]a" { t.Errorf("Process ]aa, got %s valid=%v expected true", ng.GetProcessedString(VietnameseMode), ng.IsValid(true)) } - var im = ParseInputMethod(InputMethodDefinitions, "Telex 2") + var im = ParseInputMethod(InputMethodDefinitions, "Telex") var ng = NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformEverywhere) ng.ProcessString("[", VietnameseMode) if ng.GetProcessedString(VietnameseMode) != "ơ" { - t.Errorf("Process Telex 2 [[], got [%v] expected [ươ]", ng.GetProcessedString(VietnameseMode)) + t.Errorf("Process BracketTransform [[], got [%v] expected [ươ]", ng.GetProcessedString(VietnameseMode)) } ng.Reset() ng.ProcessString("{", VietnameseMode) if ng.GetProcessedString(VietnameseMode) != "Ơ" { - t.Errorf("Process Telex 2 [{], got [%s] expected [Ơ]", ng.GetProcessedString(VietnameseMode)) + t.Errorf("Process BracketTransform [{], got [%s] expected [Ơ]", ng.GetProcessedString(VietnameseMode)) } } @@ -228,7 +231,7 @@ func TestProcessAlooString(t *testing.T) { func TestSpellingCheckForGiư(t *testing.T) { ng := newStdEngine() - // In Telex 2, ']' is used for 'ư' + // With BracketTransform, ']' is used for 'ư' ng.ProcessString("gi]", VietnameseMode) if ng.IsValid(false) == false { t.Errorf("Process gi], got [%v] expected [%v]", ng.IsValid(false) == false, true) @@ -358,8 +361,9 @@ func TestProcessTnoss(t *testing.T) { //ềng func TestProcessEenghf(t *testing.T) { - var im = ParseInputMethod(InputMethodDefinitions, "Telex 2") + var im = ParseInputMethod(InputMethodDefinitions, "Telex") ng := NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformEverywhere) ng.ProcessString("ddawks", VietnameseMode) if ng.GetProcessedString(VietnameseMode) != "đắk" { t.Errorf("Process eenghf, got [%v] expected [đắk]", ng.GetProcessedString(VietnameseMode)) @@ -414,8 +418,9 @@ func TestProcesshuoswc(t *testing.T) { //choas, bieecs, uese func TestProcesschoas(t *testing.T) { - var im = ParseInputMethod(InputMethodDefinitions, "Telex 2") + var im = ParseInputMethod(InputMethodDefinitions, "Telex") ng := NewEngine(im, EstdFlags&^EstdToneStyle) + ng.SetBracketTransformMode(BracketTransformEverywhere) ng.ProcessString("choas", VietnameseMode) if ng.GetProcessedString(VietnameseMode) != "choá" { t.Errorf("Process [choas], got [%v] expected [choá]", ng.GetProcessedString(VietnameseMode)) @@ -694,3 +699,72 @@ func BenchmarkRemoveLastChar(b *testing.B) { } } } + +func TestBracketTransformGlobal(t *testing.T) { + var im = ParseInputMethod(InputMethodDefinitions, "Telex") + var ng = NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformEverywhere) + + ng.ProcessString("[", VietnameseMode) + if ng.GetProcessedString(VietnameseMode | FullText) != "ơ" { + t.Errorf("BracketTransform (Everywhere): [ expected ơ, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } + ng.Reset() + ng.ProcessString("]", VietnameseMode) + if ng.GetProcessedString(VietnameseMode | FullText) != "ư" { + t.Errorf("BracketTransform (Everywhere): ] expected ư, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } + + ng.Reset() + ng.SetBracketTransformMode(BracketTransformNonStart) + ng.ProcessString("[", VietnameseMode) + // At start of word, [ should stay [ + if ng.GetProcessedString(VietnameseMode | FullText) != "[" { + t.Errorf("BracketTransform (NonStart) at start: [ expected [, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } + ng.Reset() + ng.ProcessString("a[", VietnameseMode) + // After a, [ should become ơ + if ng.GetProcessedString(VietnameseMode | FullText) != "aơ" { + t.Errorf("BracketTransform (NonStart) after a: [ expected aơ, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } + + // Test double typing to cancel + ng.Reset() + ng.SetBracketTransformMode(BracketTransformEverywhere) + ng.ProcessString("[[", VietnameseMode) + if ng.GetProcessedString(VietnameseMode | FullText) != "[" { + t.Errorf("BracketTransform (Everywhere) double typing: [[ expected [, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } +} + +func TestBracketTransformWithTone(t *testing.T) { + var im = ParseInputMethod(InputMethodDefinitions, "Telex") + var ng = NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformEverywhere) + + // Test m[f -> mờ + ng.ProcessString("m[f", VietnameseMode) + if ng.GetProcessedString(VietnameseMode) != "mờ" { + t.Errorf("BracketTransform + Tone: m[f expected mờ, got %s", ng.GetProcessedString(VietnameseMode)) + } + + // Test d]s -> dứ + ng.Reset() + ng.ProcessString("d]s", VietnameseMode) + if ng.GetProcessedString(VietnameseMode) != "dứ" { + t.Errorf("BracketTransform + Tone: d]s expected dứ, got %s", ng.GetProcessedString(VietnameseMode)) + } +} + +func TestBracketDisabledWithTone(t *testing.T) { + var im = ParseInputMethod(InputMethodDefinitions, "Telex") + var ng = NewEngine(im, EstdFlags) + ng.SetBracketTransformMode(BracketTransformDisabled) + + // Test m[f -> m[f (since [ is literal and not a vowel, f remains f) + ng.ProcessString("m[f", VietnameseMode) + if ng.GetProcessedString(VietnameseMode | FullText) != "m[f" { + t.Errorf("Bracket Disabled + Tone: m[f expected m[f, got %s", ng.GetProcessedString(VietnameseMode | FullText)) + } +} diff --git a/bamboo_utils.go b/bamboo_utils.go index 3d682d6..d8ac493 100644 --- a/bamboo_utils.go +++ b/bamboo_utils.go @@ -401,7 +401,7 @@ var regUhO = regexp.MustCompile(`(ưo|ươ)`) **/ func generateTransformations(composition []*Transformation, applicableRules []Rule, flags uint, lowerKey rune, isUpperCase bool) []*Transformation { var transformations []*Transformation - // Double typing an effect key undoes it and its effects, e.g. w + w -> w (Telex 2) + // Double typing an effect key undoes it and its effects, e.g. w + w -> w if len(composition) > 0 { var rule = composition[len(composition)-1].Rule if rule.EffectType == Appending && rule.Key == lowerKey && rule.Key != rule.Result { diff --git a/input_method_def.go b/input_method_def.go index bb7463f..5ed6485 100644 --- a/input_method_def.go +++ b/input_method_def.go @@ -65,27 +65,6 @@ var InputMethodDefinitions = map[string]InputMethodDefinition{ "$": "_Ô", "0": "__đ", ")": "_Đ", - "[": "__ư", - "{": "_Ư", - "]": "__ơ", - "}": "_Ơ", - }, - "Telex 2": { - "z": "XoaDauThanh", - "s": "DauSac", - "f": "DauHuyen", - "r": "DauHoi", - "x": "DauNga", - "j": "DauNang", - "a": "A_Â", - "e": "E_Ê", - "o": "O_Ô", - "w": "UOA_ƯƠĂ", - "d": "D_Đ", - "]": "__ư", - "[": "__ơ", - "}": "_Ư", - "{": "_Ơ", }, "Telex + VNI": { "z": "XoaDauThanh",