从英文官方网站看到一个自动补全的类库
eAutocomplete
github地址:https://github.com/A-AhkUser/eAutocomplete
类库源码eAutocomplete.ahk:
#Include %A_LineFile%/../acc.ahk Class eAutocomplete { ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PRIVATE NESTED CLASSES ~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Class _Proxy extends eAutocomplete._Functor { static instances := {} call(_k, _params*) { if not (eAutocomplete._Proxy.instances.hasKey(_k)) { eAutocomplete._Proxy.instances[_k] := {} } else if not (_params.length()) { return "", eAutocomplete._Proxy.instances.delete(_k) } _subClass := eAutocomplete[ SubStr(this.__class, InStr(this.__class, ".",, 0) + 1) ] return _inst := new _subClass(_k, _params*), eAutocomplete._Proxy.instances[_k].push(_inst) } } Class _Functor { ; https://autohotkey.com/boards/viewtopic.php?f=7&t=38151 __Call(_newEnum, _k, _params*) { if (_k <> "") return this.call(_k, _params*) } } ; ==================================================================================== Class _EventObject extends eAutocomplete._Proxy { __New(_source, _eventName, _callback) { this.source := _source, this.eventName := _eventName if (_callback = "") { this.unregister() } if (IsFunc(_callback)) { ((_callback.minParams = "") && _callback:=Func(_callback)) _source[_eventName] := _callback } else if (IsObject(_callback)) { _source[_eventName] := _callback } } unregister() { this.source[ this.eventName ] := "" } __Delete() { this.unregister() } } Class _Hotkey extends eAutocomplete._Proxy { __New(_hkIfFuncObject, _keyName, _func) { this.ifFuncObj := _hkIfFuncObject, this.keyName := _keyName Hotkey, If, % _hkIfFuncObject Hotkey % _keyName, % _func, On T1 I0 B0 Hotkey, If } unregister() { static _f := Func("WinActive") _hkIfFuncObject := this.ifFuncObj Hotkey, If, % _hkIfFuncObject Hotkey % this.keyName, % _f, Off Hotkey, If } __Delete() { this.unregister() } } Class _Iterator extends eAutocomplete._Proxy { __New(_ID, _fn) { this.callableObject := _fn } setPeriod(_period) { if (_f:=this.callableObject) SetTimer, % _f, % _period } unregister() { if not (_f:=this.callableObject) return SetTimer, % _f, Off SetTimer, % _f, Delete this.callableObject := "" } __Delete() { this.unregister() } } ; ==================================================================================== Class _Resource { static table := [] path := "" subsections := {} hapaxLegomena := {} _set(_sourceName, _fileFullPath:="", _resource:="") { if not (StrLen(_sourceName)) throw Exception("Invalid source name.") if (_fileFullPath <> "") { if not (FileExist(_fileFullPath)) throw Exception("The resource could not be found.") try _fileObject:=FileOpen(_fileFullPath, 4+8+0, "UTF-8") catch throw Exception("Failed attempt to open the file.") _resource := _fileObject.read(), _fileObject.close() } _source := new eAutocomplete._Resource(_sourceName) _source.path := _fileFullPath _resource .= "`n" _batchLines := A_BatchLines SetBatchLines, -1 Sort, _resource, D`n U ErrorLevel := 0 _resource := "`n" . LTrim(_resource, "`n") _listLines := A_ListLines ListLines, Off while ((_letter:=SubStr(_resource, 2, 1)) <> "") { _position := RegExMatch(_resource, "Psi)\n\Q" . _letter . "\E[^\n]+(?:.*\n\Q" . _letter . "\E.+?(?=\n))?", _length) + _length if _letter is not space _source.subsections[_letter] := SubStr(_resource, 1, _position) _resource := SubStr(_resource, _position) } ListLines % _listLines ? "On" : "Off" SetBatchLines % _batchLines } __New(_sourceName) { static _ := new eAutocomplete._Resource("Default") this.name := _sourceName return eAutocomplete._Resource.table[_sourceName] := this } appendValue(_value) { _subsections := this.subsections, _letter := SubStr(_value, 1, 1) (_subsections.hasKey(_letter) || _subsections[_letter]:="`n") _subsection := _subsections[_letter] . _value . "`n" Sort, _subsection, D`n U ErrorLevel := 0 _subsections[_letter] := "`n" . LTrim(_subsection, "`n") } update() { if (this.path <> "") { try _fileObject:=FileOpen(this.path, 4+1, "UTF-8") catch return for _letter, _subsection in this.subsections { _fileObject.write(_subsection) } _fileObject.close() } } } Class _pendingWordMatchObjectWrapper { match := {value: "", pos: 0, len: 0} leftPart := {value: "", pos: 0, len: 0} isRegEx := {value: "", pos: 0, len: 0} rightPart := {value: "", pos: 0, len: 0} isComplete := {value: "", pos: 0, len: 0} } Class _ListBox { static _properties := (LTrim Join C { AHKID: "", bkColor: "FFFFFF", fontColor: "000000", fontName: "Segoe UI", fontSize: "13", HWND: "", maxSuggestions: 7, tabStops: 512, transparency: 235 } ) static _COUNTUPPERTHRESHOLD := 52 _lastX := 0 _lastY := 0 _lastWidth := 0 _itemHeight := 0 _list := "" _itemCount := 0 _visible := false _onSelection := "" __New(_GUIID, _hHostControl) { static _virtualScreenWidth := DllCall("User32.dll\GetSystemMetrics", "UInt", 78) this._overallWidthAlloc := _virtualScreenWidth eAutocomplete._setOptions(this,, true) _GUI := A_DefaultGUI GUI, New, % "+Owner" . (this._owner:=_GUIID) . " +hwnd_parent +ToolWindow -Caption +Delimiter`n" this._host := _hHostControl, this._parent := _parent GUI, Margin, 0, 0 GUI, Add, ListBox, % "x0 y0 -HScroll +VScroll hwnd_hListBox t" . eAutocomplete._ListBox._properties.tabStops, this._AHKID := "ahk_id " . (this._HWND:=_hListBox) GUI, %_GUI%:Default this.fontName := eAutocomplete._ListBox._properties.fontName this._selection := new eAutocomplete._ListBox._SelectionWrapper(this._HWND) } __Set(_k, _v) { if (eAutocomplete._Listbox._properties.hasKey(_k)) { if ((_updateFont:=((_k = "fontName") || (_k = "fontSize"))) || (_k = "fontColor")) { ; * this["_" . _k] := _v try GUI % this._parent . ":Font", % Format("c{1} s{2}", this._fontColor, this._fontSize), % this._fontName GuiControl, Font, % this._HWND if (_updateFont) { if (this._hFont) { DllCall("SelectObject", "UPtr", this._hDC, "UPtr", this._hFont, "UPtr") DllCall("ReleaseDC", "UPtr", this._HWND, "UPtr", this._hDC) } this._hDC := DllCall("GetDC", "UPtr", this._HWND, "UPtr") SendMessage, 0x31, 0, 0,, % this._AHKID this._hFont := DllCall("SelectObject", "UPtr", this._hDC, "UPtr", ErrorLevel, "UPtr") SendMessage, 0x1A1, 0, 0,, % this._AHKID this._itemHeight := ErrorLevel this._autoWH(this._list) } return this["_" . _k] } else if (_k = "maxSuggestions") { _COUNTUPPERTHRESHOLD := eAutocomplete._ListBox._COUNTUPPERTHRESHOLD if _v between 1 and %_COUNTUPPERTHRESHOLD% return this._maxSuggestions:=Floor(_v), this._autoWH(this._list) else return this._maxSuggestions } else if (_k = "transparency") { if _v between 0 and 255 { this._transparency := _v (this._visible && this._show()) } return this._transparency } else if (_k = "bkColor") { ; * try GUI % this._parent . ":Color",, % this._bkColor:=_v return this._bkColor } else if (_k = "tabStops") { if not (mod(_v, 8)) GuiControl, % "+t" . this["_" . _k]:=_v, % this._HWND return this["_" . _k] } else if ((_k = "AHKID") || (_k = "HWND")) return this["_" . _k] } } __Get(_k, _params*) { if (eAutocomplete._Listbox._properties.hasKey(_k)) return this["_" . _k] } _hasHScrollBar { get { return !(this._itemCount <= this._maxSuggestions) } } setOptions(_opt) { eAutocomplete._setOptions(this, _opt) } _setData(_list) { _list := Trim(_list, "`n"), _count := 0 if (_list <> "") { StrReplace(_list, "`n",, _count) _upperThreshold := eAutocomplete._ListBox._COUNTUPPERTHRESHOLD if (++_count > _upperThreshold) { _count := _upperThreshold, _list := SubStr(_list, 1, InStr(_list, "`n",,, ++_upperThreshold) - 1) } } this._itemCount := _count GuiControl,, % this._HWND, % "`n" . this._list:=_list this._autoWH(_list) this._selection := new eAutocomplete._ListBox._SelectionWrapper(this._HWND) } _getHeight() { _rows := (this._hasHScrollBar) ? this._maxSuggestions : this._itemCount return (_rows + 1) * this._itemHeight } Class _SelectionWrapper { _index := 0 _text := "" __New(_parent) { this._parent := _parent } index { set { GuiControl, Choose, % this._parent, % this._index:=value return value } get { return this._index } } text { get { ControlGet, _item, Choice,,, % "ahk_id " . this._parent return this._text:=_item } set { return this.text } } offsetTop { get { SendMessage, 0x018E, 0, 0,, % "ahk_id " . this._parent return this._offsetTop := this._index - ErrorLevel } set { return this.offsetTop } } } ; ============================================================================================================= _getCurrentItemData(_dataMaxIndex:=3) { ; //LB_GETITEMDATA/LB_SETITEMDATA (_tsv:=[])[_dataMaxIndex] := "" for _index, _element in StrSplit(this._selection.text, A_Tab, A_Tab . A_Space, _dataMaxIndex) _tsv[_index] := _element return _tsv } ; ============================================================================================================= _show(_boolean:=true) { _hLastFoundWindow := WinExist() try GUI % this._parent ":+LastFound" WinSet, Transparent, % (this._visible:=_boolean) ? this._transparency : 0 WinSet, Redraw WinExist("ahk_id " . _hLastFoundWindow) } _showDropDown() { this._autoXY(), this._show() } _hideDropDown() { this._show(false) } _dismiss() { this._setData(""), this._hideDropDown() } __select(_hwnd:="", _event:="") { _selection := this._selection, _index := _selection._index if (_event = "CustomDown") { ((++_index > this._itemCount) && _index:=1) _selection.index := _index, ((_fn:=this._onSelection) && _fn.call(_selection, false, true)) } else if (_event = "CustomUp") { ((--_index < 1) && _index:=this._itemCount) _selection.index := _index, ((_fn:=this._onSelection) && _fn.call(_selection, false, true)) } else if (_event = "Normal") { SendMessage, 0x188, 0, 0,, % "ahk_id " . _hwnd _pos := (ErrorLevel << 32 >> 32) + 1 _selectionHasChanged := (_index <> _pos) _selection.index := _pos, ((_fn:=this._onSelection) && _fn.call(_selection, true, _selectionHasChanged)) } } _selectUp() { this.__select(, "CustomUp") } _selectDown() { this.__select(, "CustomDown") } _dispose() { try GUI % this._parent . ":-Parent" GuiControl -g, % this._HWND eAutocomplete._EventObject(this) } __Delete() { ; MsgBox % A_ThisFunc try GUI % this._parent . ":Destroy" DllCall("SelectObject", "UPtr", this._hDC, "UPtr", this._hFont, "UPtr") DllCall("ReleaseDC", "UPtr", this._HWND, "UPtr", this._hDC) } } Class _DropDownList extends eAutocomplete._ListBox { __New(_GUIID, _hHostControl) { base.__New(_GUIID, _hHostControl) GUI % this._parent . ":+E0x20" } _getWidth(_list) { static SM_CXVSCROLL := DllCall("GetSystemMetrics", "UInt", 2) _size := "", _w := 0 _listLines := A_ListLines ListLines, Off Loop, Parse, % _list, `n { _substr := SubStr(A_LoopField, 1, ((_l:=InStr(A_LoopField, A_Tab) - 1) > 0) ? _l : _l:=StrLen(A_LoopField)) DllCall("GetTextExtentPoint32", "UPtr", this._hDC, "Str", _substr, "Int", _l, "Int64P", _size) _size &= 0xFFFFFFFF ((_size > _w) && _w:=_size) } ListLines % _listLines ? "On" : "Off" return _w, (_w && _w += 10 + this._hasHScrollBar * SM_CXVSCROLL) } _autoWH(_params*) { _w := this._lastWidth := this._getWidth(_params.1), _h := this._getHeight() GuiControl, Move, % this._HWND, % "w" . _w . " h" . _h GUI % this._parent . ":Show", % "NA AutoSize" } _autoXY() { if (A_CaretX <> "") { VarSetCapacity(_POINT_, 8, 0) DllCall("User32.dll\GetCaretPos", "Ptr", &_POINT_) DllCall("User32.dll\MapWindowPoints", "Ptr", this._host, "Ptr", 0, "Ptr", &_POINT_, "UInt", 1) _x1 := NumGet(_POINT_, 0, "Int") + 5, _y := NumGet(_POINT_, 4, "Int") + 30 _x2 := _x1 + this._lastWidth _x := (_x2 > this._overallWidthAlloc) ? this._overallWidthAlloc - this._lastWidth : _x1 GUI % this._parent . ":Show", % "NA x" . (this._lastX:=_x) . " y" . (this._lastY:=_y) } } } Class _ComboBoxList extends eAutocomplete._ListBox { __New(_GUIID, _hHostControl) { base.__New(_GUIID, _hHostControl) GUI % this._parent . ":+Parent" . this._owner _fn := this.__select.bind(this) GuiControl, +g, % this._HWND, % _fn } _getWidth() { ControlGetPos,,, _w,,, % "ahk_id " . this._host return _w } _autoWH(_params*) { if not (WinExist("ahk_id " . this._host)) return GuiControl, MoveDraw, % this._HWND, % "h" . (_h:=this._getHeight()) . " w" . (_w:=this._getWidth()) GUI % this._parent . ":Show", % "NA AutoSize" } _autoXY() { if not (WinExist("ahk_id " . this._host)) return VarSetCapacity(_RECT_, 16, 0) ; https://autohotkey.com/boards/viewtopic.php?f=6&t=38472 - convert coordinates between Client/Screen/Window modes DllCall("User32.dll\GetWindowRect", "Ptr", this._host, "Ptr", &_RECT_) _x := NumGet(&_RECT_, 0, "Int"), _y := NumGet(&_RECT_, 4, "Int"), _h := NumGet(&_RECT_, 12, "Int") - _y this._lastX := _x, this._lastY := _y + _h DllCall("User32.dll\MapWindowPoints", "Ptr", 0, "Ptr", this._owner, "Ptr", &_RECT_, "UInt", 2) _x := NumGet(&_RECT_, 0, "Int"), _y := NumGet(&_RECT_, 4, "Int"), _h := NumGet(&_RECT_, 12, "Int") - _y GUI % this._parent . ":Show", % "NA x" . _x . " y" . _y + _h } } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PRIVATE BASE OBJECT PROPERTIES ~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static _instances := {} ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PUBLIC PROPERTIES ~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ static _properties := (LTrim Join C { AHKID: "", autoSuggest: true, collectAt: 4, collectWords: true, disabled: false, endKeys: "?!,;.:(){}[\]'""<>\\@=/|", ; +todo subsquenceKeys expandWithSpace: true, HWND: "", learnWords: false, listbox: "", matchModeRegEx: true, minWordLength: 4, onCompletion: "", onReplacement: "", onResize: "", onSuggestionLookUp: "", regExSymbol: "*", source: "", suggestAt: 2 ; +onSuggestionsAvailable: "", } ) __Set(_k, _v) { if (eAutocomplete._properties.hasKey(_k)) { if ((_k = "AHKID") || (_k = "HWND") || (_k = "listbox")) return this["_" . _k] else if ((_k = "autoSuggest") || (_k = "collectWords") || (_k = "expandWithSpace") || (_k = "learnWords") || (_k = "matchModeRegEx")) return this["_" . _k] := !!_v else if (_k = "source") { if ((_v <> this._source.name) && eAutocomplete._Resource.table.hasKey(_v)) { _state := this._disabled this.disabled := true (this._learnWords && this._source.update()) return this._source:=eAutocomplete._Resource.table[_v], this._disabled := _state } return this._source.name } else if (_k = "disabled") { ((!!_v <> this._disabled) && (this._disabled:=!!_v) && this._listbox._dismiss()) return this._disabled } else if ((_k = "onCompletion") || (_k = "onResize")) ; || (_k = "onSuggestionsAvailable") return _v, eAutocomplete._EventObject(this, "_" . _k, _v) else if ((_k = "onReplacement") || (_k = "onSuggestionLookUp")) { ((_v <> "") || _v:=this["__" . _k].bind(this)) return _v, eAutocomplete._EventObject(this, "_" . _k, _v) } else if ((_k = "collectAt") || (_k = "minWordLength") || (_k = "suggestAt")) return (not ((_v:=Floor(_v)) > 0)) ? this["_" . _k] : this["_" . _k]:=_v else if (_k = "endKeys") { static _lastEndKeys := eAutocomplete._properties.endKeys if InStr(_v, this._regExSymbol) return this["_endKeys"] _lastEndKeys := "", _endKeys := "" Loop, parse, % RegExReplace(_v, "\s") { if (InStr(_lastEndKeys, A_LoopField)) continue _lastEndKeys .= A_LoopField if A_LoopField in ^,-,],\ _endKeys .= "\" . A_LoopField else _endKeys .= A_LoopField } return this["_endKeys"] := _endKeys } else if (_k = "regExSymbol") { _v := Trim(_v, _lastEndKeys . A_Space . "`t`r`n") return (not (StrLen(_v) = 1)) ? this._regExSymbol : this._regExSymbol:=_v } } } setOptions(_opt) { eAutocomplete._setOptions(this, _opt) } __Get(_k, _params*) { if (eAutocomplete._properties.hasKey(_k)) return this["_" . _k] } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PUBLIC METHODS ~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ dispose() { if not (eAutocomplete._instances.hasKey(this._HWND)) return this.disabled := true, eAutocomplete._instances.delete(this._HWND) for _, _instance in eAutocomplete._instances, _noMoreFromProcess := true { if (_instance._idProcess = this._idProcess) { _noMoreFromProcess := false break } } (_noMoreFromProcess && eAutocomplete._Value.unregisterProc(this._idProcess)) ; ======================================== eAutocomplete._Hotkey(this._hkIfFuncObject) eAutocomplete._EventObject(this) eAutocomplete._Iterator(this._HWND) ; ======================================== (this._learnWords && this._source.update()) if (this.hasKey("_hEditLowerCornerHandle")) GuiControl, -g, % this._hEditLowerCornerHandle this._listbox._dispose() } __Delete() { ; MsgBox % A_ThisFunc } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PUBLIC BASE OBJECT METHODS ~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ create(_GUIID, _editOptions:="", _opt:="") { _hLastFoundWindow := WinExist() try { Gui % _GUIID . ":+LastFoundExist" IfWinNotExist throw Exception("Invalid GUI window.",, _GUIID) } finally WinExist("ahk_id " . _hLastFoundWindow) GUI, % _GUIID . ":Add", Edit, % _editOptions . " hwnd_hEdit", _plusResize := ((_editOptions <> "") && (_editOptions ~= "i)(^|\s)\K\+?[^-]?Resize(?=\s|$)")) _isComboBox := (!_plusResize && !(DllCall("GetWindowLong", "UInt", _hEdit, "Int", -16) & 0x4)) _inst := new eAutocomplete(_GUIID, _hEdit, _isComboBox, _opt) if (_plusResize) { GuiControlGet, _pos, Pos, % _hEdit GUI, % _GUIID . ":Add", Text, % "0x12 w11 h11 " . Format("x{1} y{2}", _posx + _posw - 7, _posy + _posh - 7) . " hwnd_hEditLowerCornerHandle", _inst._hEditLowerCornerHandle := _hEditLowerCornerHandle, _fn := _inst.__resize.bind(_inst) GuiControl +g, % _hEditLowerCornerHandle, % _fn } return _inst } attach(_hHostControl, _opt:="") { _detectHiddenWindows := A_DetectHiddenWindows DetectHiddenWindows, On WinGetClass, _class, % "ahk_id " . _hHostControl DetectHiddenWindows % _detectHiddenWindows if not ((_class = "Edit") || (_class = "RICHEDIT50W")) throw Exception("The host control either does not exist or is not a representative of the class Edit/RICHEDIT50W.") _GUIID := DllCall("User32.dll\GetAncestor", "Ptr", _hHostControl, "UInt", 2, "Ptr") ControlGet, _style, Style,,, % "ahk_id " . _hHostControl _isComboBox := not (_style & 0x4) ; ES_MULTILINE _inst := new eAutocomplete(_GUIID, _hHostControl, _isComboBox, _opt) if (WinActive("ahk_id " . _GUIID)) eAutocomplete._Value.__sourceChanged(0x8005, _hHostControl) return _inst } setSourceFromVar(_sourceName, _list:="") { eAutocomplete._Resource._set(_sourceName, "", _list) } setSourceFromFile(_sourceName, _fileFullPath) { eAutocomplete._Resource._set(_sourceName, _fileFullPath) } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PRIVATE PROPERTIES ~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ _hEditLowerCornerHandle := "" _minSize := {w: 51, h: 21} _maxSize := {w: A_ScreenWidth, h: A_ScreenHeight} _content := "" _pendingWord := "" _ready := true _completionData := "" ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PRIVATE METHODS ~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __New(_GUIID, _hHostControl, _isComboBox, _opt:="") { eAutocomplete._setOptions(this,, true) _idProcess := "", DllCall("User32.dll\GetWindowThreadProcessId", "Ptr", _GUIID, "UIntP", _idProcess, "UInt") if not (_idProcess) throw Exception("Could not retrieve the identifier of the process that created the host window.") this._idProcess := _idProcess, this._parent := _GUIID, this._AHKID := "ahk_id " . (this._HWND:=_hHostControl) _listBox := (this._isComboBox:=_isComboBox) ? eAutocomplete._ComboBoxList : eAutocomplete._DropDownList _listbox := this._listbox := new _listBox(_GUIID, _hHostControl) this._source := eAutocomplete._Resource.table["Default"] this.onSuggestionLookUp := "", this.onReplacement := "" if (IsObject(_opt)) { _clone := _opt.clone() if (_listBoxOptions:=_clone.remove("listbox")) _listBox.setOptions(_listBoxOptions) this.setOptions(_clone) } ; ======================================== eAutocomplete._Iterator(this._HWND, ObjBindMethod(this, "__valueChanged", _hHostControl)) ; ======================================== eAutocomplete._Value.registerProc(this._idProcess) eAutocomplete._EventObject(this._listbox, "_onSelection", ObjBindMethod(this, "_listboxSelectionEventMonitor")) ; ======================================== _hkIfFuncObject := this._hkIfFuncObject := this._hotkeyPressHandler.bind("", _hHostControl) eAutocomplete._Hotkey(_hkIfFuncObject, "Escape", ObjBindMethod(this._listbox, "_dismiss")) eAutocomplete._Hotkey(_hkIfFuncObject, "Up", ObjBindMethod(_listbox, "_selectUp")) eAutocomplete._Hotkey(_hkIfFuncObject, "Down", ObjBindMethod(_listbox, "_selectDown")) eAutocomplete._Hotkey(_hkIfFuncObject, "Left", Func("WinActive")) eAutocomplete._Hotkey(_hkIfFuncObject, "Right", ObjBindMethod(this, "_completionDataLookUp", 1)) eAutocomplete._Hotkey(_hkIfFuncObject, "+Right", ObjBindMethod(this, "_completionDataLookUp", 2)) eAutocomplete._Hotkey(_hkIfFuncObject, "Tab", ObjBindMethod(this, "_complete", false, 1)) eAutocomplete._Hotkey(_hkIfFuncObject, "+Tab", ObjBindMethod(this, "_complete", false, 2)) eAutocomplete._Hotkey(_hkIfFuncObject, "Enter", ObjBindMethod(this, "_complete", true, 1)) eAutocomplete._Hotkey(_hkIfFuncObject, "+Enter", ObjBindMethod(this, "_complete", true, 2)) ; ======================================== return eAutocomplete._instances[_hHostControl] := this } ; ================================================================== __valueChanged() { this._ready := false _count := this._hasSuggestions if (_count > 0) { (this._autoSuggest && this._suggest()) ; +(this._onSuggestionsAvailable && this._onSuggestionsAvailable.call(this)) } else if (_count = -1) { _match := this._pendingWord.match.value if (_match <> this._listbox._getCurrentItemData().1) this.__hapax(_match, this._pendingWord.match.len) this._listbox._hideDropDown() } else { ; 0 this._listbox._hideDropDown() } this._ready := true } _capturePendingWord() { _wrapper := new eAutocomplete._pendingWordMatchObjectWrapper _content := this._content, _caretPos := this._getSelection() ; <<<< _caretIsWellPositioned := (StrLen(RegExReplace(SubStr(_content, _caretPos, 2), "\s$")) <= 1) ; <<<<- if not (_caretIsWellPositioned) return _wrapper _leftPart := "?P<leftPart>[^\s" . this._endKeys . this._regExSymbol . "]{" . this._suggestAt - 1 . ",}" ; todo: prebuild the needleRegEx _isRegex := "?P<isRegEx>\Q" . this._regExSymbol . "\E?" _rightPart := "?P<rightPart>[^\s" . this._endKeys . this._regExSymbol . "]+" _match := "?P<match>(" . _leftPart . ")(" . _isRegex . ")(" . _rightPart . ")" _isComplete := "?P<isComplete>[\s" . this._endKeys . "]?" RegExMatch(SubStr(_content, 1, _caretPos), "`aOi)(" . _match . ")(" . _isComplete . ")$", _pendingWord) for _subPatternName, _subPatternObject in _wrapper for _property in _subPatternObject, _o := _wrapper[_subPatternName] _o[_property] := _pendingWord[_property](_subPatternName) return _wrapper } __hapax(_match, _len) { if (!this._collectWords || (_len < this._minWordLength)) return _hapaxLegomena := this._source.hapaxLegomena (_hapaxLegomena.hasKey(_match) || _hapaxLegomena[_match]:=0) if not (++_hapaxLegomena[_match] = this.collectAt) return this._source.appendValue(_match) } _hasSuggestions { get { _pendingWord := this._pendingWord := this._capturePendingWord() if not (this._pendingWord.match.len) return 0 else if (_pendingWord.isComplete.len) return -1 _list := "" if ((_subsection:=this._source.subsections[ SubStr(_pendingWord.match.value, 1, 1) ]) <> "") { if (_pendingWord.isRegEx.len && this._matchModeRegEx) { _substring := _pendingWord.leftPart.value RegExMatch(_subsection, "`nsi)\n\Q" . _substring . "\E[^\n]+(?:.*\n\Q" . _substring . "\E.+?(?=\n))?", _match) _len := _pendingWord.leftPart.len + 1, _rightPart := _pendingWord.rightPart.value _listLines := A_ListLines ListLines, Off Loop, parse, % _match, `n { if (InStr(SubStr(A_LoopField, _len), _rightPart)) _list .= A_LoopField . "`n" } ListLines % _listLines ? "On" : "Off" } else { _substring := _pendingWord.match.value RegExMatch(_subsection, "`nsi)\n\Q" . _substring . "\E[^\n]+(?:.*\n\Q" . _substring . "\E.+?(?=\n))?", _list) } } this._listbox._setData(_list) return this._listbox._itemCount } } _suggest() { ; if (!this._isComboBox || !this._listbox._visible) this._listbox._showDropDown() this._listbox._selectDown() } _listboxSelectionEventMonitor(_selection, _clickEvent, _selectionHasChanged) { (_selectionHasChanged && this._completionData:=""), (_clickEvent && this._complete(false)) } _completionDataLookUp(_tabIndex) { static _keys := {} if not (_keys.hasKey(A_ThisHotkey)) { RegExMatch(A_ThisHotkey, "i)(\w+|.)(?:[ `t]Up)?$", _match), _keys[ A_ThisHotkey ] := _match } _listbox := this._listbox (!this._completionData && this._completionData:=_listbox._getCurrentItemData()) _coordModeToolTip := A_CoordModeToolTip CoordMode, ToolTip, Screen _x := _listbox._lastX + 10 _y := _listbox._lastY + (_listbox._selection.offsetTop - 0.5) * _listbox._itemHeight ToolTip % this._onSuggestionLookUp.call(this._completionData.1, _tabIndex), % _x, % _y KeyWait % _keys[ A_ThisHotkey ] ToolTip CoordMode, ToolTip, % _coordModeToolTip } __onSuggestionLookUp(_value, _tabIndex) { _infoTipText := this._completionData[ _tabIndex + 1 ] return StrReplace(StrReplace(_infoTipText, "``n", "`n"), "``r", "`r") } _complete(_goToNewLine, _tabIndex:=1) { static _keys := {} if (_isReplacement:=A_ThisHotkey <> "") { if not (_keys.hasKey(A_ThisHotkey)) { RegExMatch(A_ThisHotkey, "i)(\w+|.)(?:[ `t]Up)?$", _match), _keys[ A_ThisHotkey ] := _match } KeyWait % _keys[ A_ThisHotkey ], T0.6 (not (_isReplacement:=ErrorLevel)) ? this._expand() : this._replace(_tabIndex) KeyWait % _keys[ A_ThisHotkey ] } else this._expand() this._listbox._dismiss() if (_goToNewLine) { eAutocomplete._Value._rawSend("{Enter}", this._HWND) } else (this._expandWithSpace && eAutocomplete._Value._rawSend("{Space}", this._HWND)) ((_fn:=this._onCompletion) && _fn.call(this, this._completionData[ 1 + _isReplacement * _tabIndex ], _isReplacement)) } _expand() { (!this._completionData && this._completionData:=this._listbox._getCurrentItemData()) _pendingWord := this._pendingWord if (_pendingWord.isRegEx.len) { StringTrimLeft, _missingPart, % this._completionData.1, % _pendingWord.leftPart.len eAutocomplete._Value._rawSend("{BS " . 1 + _pendingWord.rightPart.len . "}", this._HWND) } else { StringTrimLeft, _missingPart, % this._completionData.1, % StrLen(_pendingWord.match.value) } eAutocomplete._Value._rawPaste(_missingPart, this._HWND) ; <<<< } _replace(_tabIndex) { (!this._completionData && this._completionData:=this._listbox._getCurrentItemData()) eAutocomplete._Value._rawSend("{BS " . StrLen(this._pendingWord.match.value) . "}", this._HWND) _value := this._completionData[ _tabIndex + 1 ] := this._onReplacement.call(this._completionData.1, _tabIndex) eAutocomplete._Value._rawPaste(_value, this._HWND) } __onReplacement(_value, _tabIndex) { return this._completionData[ _tabIndex + 1 ] } ; ----------------------------------------------------------------------------------------------- _getSelection(ByRef _startSel:="", ByRef _endSel:="") { ; <<<< static EM_GETSEL := 0xB0 VarSetCapacity(_startPos, 4, 0), VarSetCapacity(_endPos, 4, 0) SendMessage, % EM_GETSEL, &_startPos, &_endPos,, % this._AHKID _startSel := NumGet(_startPos), _endSel := NumGet(_endPos) return _endSel } ; ----------------------------------------------------------------------------------------------- __resize(_hEditLowerCornerHandle) { _listLines := A_ListLines ListLines, Off _coordModeMouse := A_CoordModeMouse CoordMode, Mouse, Client GuiControlGet, _start, Pos, % _hEdit:=this._HWND _minSz := this._minSize, _maxSz := this._maxSize while (GetKeyState("LButton", "P")) { MouseGetPos, _x, _y _w := _x - _startX, _h := _y - _startY if (_w <= _minSz.w) _w := _minSz.w else if (_w >= _maxSz.w) _w := _maxSz.w if (_h <= _minSz.h) _h := _minSz.h else if (_h >= _maxSz.h) _h := _maxSz.h if (this._onResize && this._onResize.call(A_GUI, this, _w, _h, _x, _y)) Exit GuiControl, Move, % _hEdit, % "w" . _w . " h" . _h GuiControlGet, _pos, Pos, % _hEdit GuiControl, MoveDraw, % _hEditLowerCornerHandle, % "x" . (_posx + _posw - 7) . " y" . _posy + _posh - 7 sleep, 15 } CoordMode, Mouse, % _coordModeMouse ListLines % _listLines ? "On" : "Off" } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~ PRIVATE BASE OBJECT METHODS ~~~~~~~~~~~~ ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ _setOptions(_obj, _opt:="", _initialize:=false) { _clone := _obj._properties.clone() if (_initialize) { for _key, _defaultValue in _clone { ObjRawSet(_obj, "_" . _key, _defaultValue) } return } if (IsObject(_opt)) for _key, _value in _opt (_clone.hasKey(_key) && _obj[_key]:=_value) } _hotkeyPressHandler(_hwnd, _thisHotkey) { if (_hwnd <> eAutocomplete._Value.lastFoundControl) return false _inst := eAutocomplete._instances[_hwnd] if (_inst._disabled || !_inst._ready || !WinActive("ahk_id " . _inst._parent)) return false _listbox := _inst._listbox if not (_listbox._visible) { if ((_thisHotkey = "Down") && _listbox._itemCount) return true, _inst._listbox._showDropDown() return false } return true } Class _Value { static processes := {} static lastFoundControl := 0x0 static bypassToggle := false registerProc(_idProcess) { if not (eAutocomplete._Value.processes.hasKey(_idProcess)) { eAutocomplete._Value.processes[_idProcess] := [] _callback := RegisterCallback("eAutocomplete._Value.__valueChange") _hWinEventHook := DllCall("User32.dll\SetWinEventHook","Uint",0x800E,"Uint",0x800E,"Ptr",0,"Ptr",_callback,"Uint",_idProcess,"Uint",0,"Uint",0) eAutocomplete._Value.processes[_idProcess].push(_hWinEventHook) _callback := RegisterCallback("eAutocomplete._Value.__sourceChanged") Loop, parse, % "0x000A,0x0015,0x8005", CSV { _hWinEventHook := DllCall("User32.dll\SetWinEventHook","Uint",A_LoopField,"Uint",A_LoopField,"Ptr",0,"Ptr",_callback,"Uint",_idProcess,"Uint",0,"Uint",0) eAutocomplete._Value.processes[_idProcess].push(_hWinEventHook) } } } unregisterProc(_idProcess) { for _, _hWinEventHook in eAutocomplete._Value.processes[_idProcess] DllCall("User32.dll\UnhookWinEvent", "Ptr", _hWinEventHook) eAutocomplete._Value.processes.delete(_idProcess) } _rawSend(_keys, _hwnd) { _state := eAutocomplete._Value.bypassToggle eAutocomplete._Value.bypassToggle := true _keyDelay := A_KeyDelay SetKeyDelay, 0 ControlSend,, % _keys, % "ahk_id " . _hwnd sleep, 300 ; check messages of the internal message queue SetKeyDelay % _keyDelay eAutocomplete._Value.bypassToggle := _state } _rawPaste(_text, _hwnd) { _state := eAutocomplete._Value.bypassToggle eAutocomplete._Value.bypassToggle := true Control, EditPaste, % StrReplace(StrReplace(_text, "``n", "`n"), "``r", "`r"),, % "ahk_id " . _hwnd sleep, 300 ; check messages of the internal message queue eAutocomplete._Value.bypassToggle := _state } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __sourceChanged(_event, _hwnd) { if ((_event = 0x8005) || (_event = 0x0015)) { ; * ControlGetFocus, _focusedControl, A ControlGet, _hwnd, Hwnd,, % _focusedControl, A if (_hwnd = eAutocomplete._Value.lastFoundControl) return } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; f(_hwnd) for _each, _instance in eAutocomplete._instances { if (_instance._listbox._visible) { _instance._listbox._hideDropDown() break } } eAutocomplete._Value.lastFoundControl:=(eAutocomplete._instances.hasKey(_hwnd)) ? _hwnd : 0x0 ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } __valueChange(_event, _hwnd, _idObject, _idChild) { static OBJID_CLIENT := 0xFFFFFFFC if (_idObject <> OBJID_CLIENT) ; for instance > OBJID_VSCROLL = 0xFFFFFFFB return if not (eAutocomplete._Value.bypassToggle) { ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; f(_hwnd) if (eAutocomplete._instances.hasKey(_hwnd)) { _inst := eAutocomplete._instances[_hwnd] Critical ; ACC ================================================ _acc := Acc_ObjectFromEvent(_idChild_, _hwnd, _idObject, _idChild) try _inst._content := _acc.accValue(0) ; ACC ================================================ ; ControlGetText, _text,, % _inst._AHKID ; _inst._content := _text if (!_inst._disabled) { eAutocomplete._Iterator.instances[_inst._HWND].1.setPeriod(-(3 + !_inst._ready) * 75) } } ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } } } }
包含的Acc.ahk
; see also: https://github.com/Drugoy/Autohotkey-scripts-.ahk/blob/master/Libraries/Acc.ahk Acc_Init() { static _h := "" IfNotEqual, _h,, return _h := DllCall("LoadLibrary", "Str", "Oleacc.dll", "UPtr") } Acc_ObjectFromEvent(ByRef _idChild_, _hWnd, _idObject, _idChild) { static VT_DISPATCH := 9 Acc_Init(), _pAcc := "" if (DllCall("Oleacc.dll\AccessibleObjectFromEvent", "Ptr", _hWnd, "UInt", _idObject, "UInt", _idChild, "PtrP", _pAcc, "Ptr", VarSetCapacity(_varChild, 8 + 2 * A_PtrSize, 0) * 0 + &_varChild) = 0) return ComObj(VT_DISPATCH, _pAcc, 1), _idChild_ := NumGet(_varChild, 8, "UInt") }
测试例子:
#NoEnv #SingleInstance force SetWorkingDir % A_ScriptDir SendMode, Input #Warn ; Windows 8.1 64 bit - Autohotkey v1.1.29.01 32-bit Unicode #Include %A_ScriptDir%\eAutocomplete.ahk for source, url in {"Autocompletion_en": "https://raw.githubusercontent.com/A-AhkUser/keypad-library/master/Keypad/Autocompletion/en" , "Autocompletion_fr": "https://raw.githubusercontent.com/A-AhkUser/keypad-library/master/Keypad/Autocompletion/fr"} { if not (FileExist(filename:=A_ScriptDir . "\" . source . ".txt")) UrlDownloadToFile % url, % filename eAutocomplete.setSourceFromFile(source, filename) } var = ( Laurène%A_Tab%test%A_Tab%blabla Loredana Francia ) eAutocomplete.setSourceFromVar("source_test", var) GUI, +Resize +hwndGUIID GUI, Font, s14, Segoe UI GUI, Color, White, White options := (LTrim Join C { autoSuggest: true, collectAt: 4, collectWords: true, disabled: false, endKeys: "?!,;.:(){}[]'""<>\@=/|", expandWithSpace: true, learnWords: true, listbox: { bkColor: "FFFFFF", fontColor: "000000", fontName: "Segoe UI", fontSize: "14", maxSuggestions: 7, transparency: 220 }, matchModeRegEx: true, minWordLength: 4, onCompletion: "test_onCompletion", onReplacement: "test_onReplacement", onResize: "test_onResize", onSuggestionLookUp: "test_onSelectionLookUp", regExSymbol: "*", source: "Autocompletion_en", suggestAt: 2 } ) A := eAutocomplete.create(GUIID, "w300 h200 +Resize", options) ; ListLines ; Pause ; ================================================= options2 := (LTrim Join C { autoSuggest: true, collectAt: 2, collectWords: true, disabled: false, endKeys: "", expandWithSpace: false, learnWords: false, matchModeRegEx: false, minWordLength: 5, onCompletion: "", onReplacement: "", onResize: "", onSuggestionLookUp: "", regExSymbol: "*", source: "Autocompletion_fr", suggestAt: 1 } ) B := eAutocomplete.create(GUIID, "x320 y10 w300") B.setOptions(options2) B.listbox.setOptions({bkColor: "1a4356" , fontColor: "FFFFFF" , fontName: "Segoe UI" , fontSize: "12" , maxSuggestions: 11 , transparency: 130 , tabStops: 64}) GUI, Add, Edit, w200 h200 GUI, Show, h500, eAutocomplete OnExit, handleExit return !i:: str := "" for k, v in options { if (IsObject(v)) { str .= ".listbox`r`n" for i in v str .= A_Tab i A_Tab " ~ " A_Tab (A.listbox)[i] "`r`n" } else str .= ((k = "source") || (SubStr(k, 1, 2) = "on")) ? k A_Tab " ~ " A_Tab A[k].name "`r`n" : k A_Tab " ~ " A_Tab A[k] "`r`n" } MsgBox % str return handleExit: A.dispose() B.dispose() ExitApp test_onReplacement(_suggestionText, _tabIndex) { return "[" _suggestionText "] from " A_ThisFunc " (" _tabIndex ")" } test_onCompletion(_instance, _text, _isRemplacement) { ToolTip % A_ThisFunc " `r`n " _instance.HWND+0 " `r`n " _text "[" _isRemplacement "]" } test_onResize(_GUI, _instance, _w, _h, _x, _y) { ToolTip % A_ThisFunc "|" _instance.HWND+0 "," _w "," _h "," _x "," _y } test_onSelectionLookUp(_suggestionText, _tabIndex) { return _suggestionText "[" _tabIndex "] from " A_ThisFunc } test_onReplacement2(_suggestionText, _tabIndex) { return "[" _suggestionText "] from " A_ThisFunc " (" _tabIndex ")" } test_onCompletion2(_instance, _text, _isRemplacement) { ToolTip % A_ThisFunc " `r`n " _instance.HWND+0 " `r`n " _text "[" _isRemplacement "]" } test_onResize2(_GUI, _instance, _w, _h, _x, _y) { ToolTip % A_ThisFunc "|" _instance.HWND+0 "," _w "," _h "," _x "," _y } test_onSelectionLookUp2(_suggestionText, _tabIndex) { return _suggestionText "[" _tabIndex "] from " A_ThisFunc } !s:: A.autoSuggest := !A.autoSuggest A.collectAt := 1 A.collectWords := !A.collectWords ; A.disabled := !A.disabled A.endKeys := "@" A.expandWithSpace := !A.expandWithSpace A.learnWords := !A.learnWords A.matchModeRegEx := !A.matchModeRegEx A.minWordLength := 3 A.listbox.maxSuggestions := 2 A.listbox.transparency := 110 A.listbox.tabStops := 8 A.onCompletion := "test_onCompletion2" A.onReplacement := "test_onReplacement2" A.onResize := "test_onResize2" A.onSuggestionLookUp := "test_onSelectionLookUp2" A.regExSymbol := "+" A.source := "source_test" A.suggestAt := 1 return
其它一些自动补全类库:
https://autohotkey.com/board/topic/96129-ahk-l-custom-autocompletion-for-edit-control-with-drop-down-list
在notepad测试效果不好
https://www.autohotkey.com/boards/viewtopic.php?f=6&t=48915&p=217792&hilit=auto+completion#p217792
速度很快,效果很好
https://github.com/Uberi/Autocomplete
这个网站有分享过