"""An auto-completion window for IDLE, used by the autocomplete extension"""import platformfrom tkinter import *from tkinter.ttk import Frame, Scrollbarfrom idlelib.autocomplete import COMPLETE_FILES, COMPLETE_ATTRIBUTESfrom idlelib.multicall import MC_SHIFTHIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"HIDE_FOCUS_OUT_SEQUENCE = "<FocusOut>"HIDE_SEQUENCES = (HIDE_FOCUS_OUT_SEQUENCE, "<ButtonPress>")KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"# We need to bind event beyond <Key> so that the function will be called# before the default specific IDLE functionKEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>","<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>","<Key-Prior>", "<Key-Next>")KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"KEYRELEASE_SEQUENCE = "<KeyRelease>"LISTUPDATE_SEQUENCE = "<B1-ButtonRelease>"WINCONFIG_SEQUENCE = "<Configure>"DOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>"class AutoCompleteWindow:def __init__(self, widget):# The widget (Text) on which we place the AutoCompleteWindowself.widget = widget# The widgets we createself.autocompletewindow = self.listbox = self.scrollbar = None# The default foreground and background of a selection. Saved because# they are changed to the regular colors of list items when the# completion start is not a prefix of the selected completionself.origselforeground = self.origselbackground = None# The list of completionsself.completions = None# A list with more completions, or Noneself.morecompletions = None# The completion mode. Either autocomplete.COMPLETE_ATTRIBUTES or# autocomplete.COMPLETE_FILESself.mode = None# The current completion start, on the text box (a string)self.start = None# The index of the start of the completionself.startindex = None# The last typed start, used so that when the selection changes,# the new start will be as close as possible to the last typed one.self.lasttypedstart = None# Do we have an indication that the user wants the completion window# (for example, he clicked the list)self.userwantswindow = None# event idsself.hideid = self.keypressid = self.listupdateid = self.winconfigid \= self.keyreleaseid = self.doubleclickid = None# Flag set if last keypress was a tabself.lastkey_was_tab = Falsedef _change_start(self, newstart):min_len = min(len(self.start), len(newstart))i = 0while i < min_len and self.start[i] == newstart[i]:i += 1if i < len(self.start):self.widget.delete("%s+%dc" % (self.startindex, i),"%s+%dc" % (self.startindex, len(self.start)))if i < len(newstart):self.widget.insert("%s+%dc" % (self.startindex, i),newstart[i:])self.start = newstartdef _binary_search(self, s):"""Find the first index in self.completions where completions[i] isgreater or equal to s, or the last index if there is no suchone."""i = 0; j = len(self.completions)while j > i:m = (i + j) // 2if self.completions[m] >= s:j = melse:i = m + 1return min(i, len(self.completions)-1)def _complete_string(self, s):"""Assuming that s is the prefix of a string in self.completions,return the longest string which is a prefix of all the strings whichs is a prefix of them. If s is not a prefix of a string, return s."""first = self._binary_search(s)if self.completions[first][:len(s)] != s:# There is not even one completion which s is a prefix of.return s# Find the end of the range of completions where s is a prefix of.i = first + 1j = len(self.completions)while j > i:m = (i + j) // 2if self.completions[m][:len(s)] != s:j = melse:i = m + 1last = i-1if first == last: # only one possible completionreturn self.completions[first]# We should return the maximum prefix of first and lastfirst_comp = self.completions[first]last_comp = self.completions[last]min_len = min(len(first_comp), len(last_comp))i = len(s)while i < min_len and first_comp[i] == last_comp[i]:i += 1return first_comp[:i]def _selection_changed(self):"""Should be called when the selection of the Listbox has changed.Updates the Listbox display and calls _change_start."""cursel = int(self.listbox.curselection()[0])self.listbox.see(cursel)lts = self.lasttypedstartselstart = self.completions[cursel]if self._binary_search(lts) == cursel:newstart = ltselse:min_len = min(len(lts), len(selstart))i = 0while i < min_len and lts[i] == selstart[i]:i += 1newstart = selstart[:i]self._change_start(newstart)if self.completions[cursel][:len(self.start)] == self.start:# start is a prefix of the selected completionself.listbox.configure(selectbackground=self.origselbackground,selectforeground=self.origselforeground)else:self.listbox.configure(selectbackground=self.listbox.cget("bg"),selectforeground=self.listbox.cget("fg"))# If there are more completions, show them, and call me again.if self.morecompletions:self.completions = self.morecompletionsself.morecompletions = Noneself.listbox.delete(0, END)for item in self.completions:self.listbox.insert(END, item)self.listbox.select_set(self._binary_search(self.start))self._selection_changed()def show_window(self, comp_lists, index, complete, mode, userWantsWin):"""Show the autocomplete list, bind events.If complete is True, complete the text, and if there is exactly onematching completion, don't open a list."""# Handle the start we already haveself.completions, self.morecompletions = comp_listsself.mode = modeself.startindex = self.widget.index(index)self.start = self.widget.get(self.startindex, "insert")if complete:completed = self._complete_string(self.start)start = self.startself._change_start(completed)i = self._binary_search(completed)if self.completions[i] == completed and \(i == len(self.completions)-1 orself.completions[i+1][:len(completed)] != completed):# There is exactly one matching completionreturn completed == startself.userwantswindow = userWantsWinself.lasttypedstart = self.start# Put widgets in placeself.autocompletewindow = acw = Toplevel(self.widget)# Put it in a position so that it is not seen.acw.wm_geometry("+10000+10000")# Make it floatacw.wm_overrideredirect(1)try:# This command is only needed and available on Tk >= 8.4.0 for OSX# Without it, call tips intrude on the typing process by grabbing# the focus.acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,"help", "noActivates")except TclError:passself.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,exportselection=False)for item in self.completions:listbox.insert(END, item)self.origselforeground = listbox.cget("selectforeground")self.origselbackground = listbox.cget("selectbackground")scrollbar.config(command=listbox.yview)scrollbar.pack(side=RIGHT, fill=Y)listbox.pack(side=LEFT, fill=BOTH, expand=True)acw.lift() # work around bug in Tk 8.5.18+ (issue #24570)# Initialize the listbox selectionself.listbox.select_set(self._binary_search(self.start))self._selection_changed()# bind eventsself.hideaid = acw.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)self.hidewid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)acw.event_add(HIDE_VIRTUAL_EVENT_NAME, HIDE_FOCUS_OUT_SEQUENCE)for seq in HIDE_SEQUENCES:self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,self.keypress_event)for seq in KEYPRESS_SEQUENCES:self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,self.keyrelease_event)self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,self.listselect_event)self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event)self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE,self.doubleclick_event)return Nonedef winconfig_event(self, event):if not self.is_active():return# Position the completion list windowtext = self.widgettext.see(self.startindex)x, y, cx, cy = text.bbox(self.startindex)acw = self.autocompletewindowacw_width, acw_height = acw.winfo_width(), acw.winfo_height()text_width, text_height = text.winfo_width(), text.winfo_height()new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width))new_y = text.winfo_rooty() + yif (text_height - (y + cy) >= acw_height # enough height belowor y < acw_height): # not enough height above# place acw below current linenew_y += cyelse:# place acw above current linenew_y -= acw_heightacw.wm_geometry("+%d+%d" % (new_x, new_y))if platform.system().startswith('Windows'):# See issue 15786. When on Windows platform, Tk will misbehave# to call winconfig_event multiple times, we need to prevent this,# otherwise mouse button double click will not be able to used.acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid)self.winconfigid = Nonedef _hide_event_check(self):if not self.autocompletewindow:returntry:if not self.autocompletewindow.focus_get():self.hide_window()except KeyError:# See issue 734176, when user click on menu, acw.focus_get()# will get KeyError.self.hide_window()def hide_event(self, event):# Hide autocomplete list if it exists and does not have focus or# mouse click on widget / text area.if self.is_active():if event.type == EventType.FocusOut:# On Windows platform, it will need to delay the check for# acw.focus_get() when click on acw, otherwise it will return# None and close the windowself.widget.after(1, self._hide_event_check)elif event.type == EventType.ButtonPress:# ButtonPress event only bind to self.widgetself.hide_window()def listselect_event(self, event):if self.is_active():self.userwantswindow = Truecursel = int(self.listbox.curselection()[0])self._change_start(self.completions[cursel])def doubleclick_event(self, event):# Put the selected completion in the text, and close the listcursel = int(self.listbox.curselection()[0])self._change_start(self.completions[cursel])self.hide_window()def keypress_event(self, event):if not self.is_active():return Nonekeysym = event.keysymif hasattr(event, "mc_state"):state = event.mc_stateelse:state = 0if keysym != "Tab":self.lastkey_was_tab = Falseif (len(keysym) == 1 or keysym in ("underscore", "BackSpace")or (self.mode == COMPLETE_FILES and keysym in("period", "minus"))) \and not (state & ~MC_SHIFT):# Normal editing of textif len(keysym) == 1:self._change_start(self.start + keysym)elif keysym == "underscore":self._change_start(self.start + '_')elif keysym == "period":self._change_start(self.start + '.')elif keysym == "minus":self._change_start(self.start + '-')else:# keysym == "BackSpace"if len(self.start) == 0:self.hide_window()return Noneself._change_start(self.start[:-1])self.lasttypedstart = self.startself.listbox.select_clear(0, int(self.listbox.curselection()[0]))self.listbox.select_set(self._binary_search(self.start))self._selection_changed()return "break"elif keysym == "Return":self.complete()self.hide_window()return 'break'elif (self.mode == COMPLETE_ATTRIBUTES and keysym in("period", "space", "parenleft", "parenright", "bracketleft","bracketright")) or \(self.mode == COMPLETE_FILES and keysym in("slash", "backslash", "quotedbl", "apostrophe")) \and not (state & ~MC_SHIFT):# If start is a prefix of the selection, but is not '' when# completing file names, put the whole# selected completion. Anyway, close the list.cursel = int(self.listbox.curselection()[0])if self.completions[cursel][:len(self.start)] == self.start \and (self.mode == COMPLETE_ATTRIBUTES or self.start):self._change_start(self.completions[cursel])self.hide_window()return Noneelif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \not state:# Move the selection in the listboxself.userwantswindow = Truecursel = int(self.listbox.curselection()[0])if keysym == "Home":newsel = 0elif keysym == "End":newsel = len(self.completions)-1elif keysym in ("Prior", "Next"):jump = self.listbox.nearest(self.listbox.winfo_height()) - \self.listbox.nearest(0)if keysym == "Prior":newsel = max(0, cursel-jump)else:assert keysym == "Next"newsel = min(len(self.completions)-1, cursel+jump)elif keysym == "Up":newsel = max(0, cursel-1)else:assert keysym == "Down"newsel = min(len(self.completions)-1, cursel+1)self.listbox.select_clear(cursel)self.listbox.select_set(newsel)self._selection_changed()self._change_start(self.completions[newsel])return "break"elif (keysym == "Tab" and not state):if self.lastkey_was_tab:# two tabs in a row; insert current selection and close acwcursel = int(self.listbox.curselection()[0])self._change_start(self.completions[cursel])self.hide_window()return "break"else:# first tab; let AutoComplete handle the completionself.userwantswindow = Trueself.lastkey_was_tab = Truereturn Noneelif any(s in keysym for s in ("Shift", "Control", "Alt","Meta", "Command", "Option")):# A modifier key, so ignorereturn Noneelif event.char and event.char >= ' ':# Regular character with a non-length-1 keycodeself._change_start(self.start + event.char)self.lasttypedstart = self.startself.listbox.select_clear(0, int(self.listbox.curselection()[0]))self.listbox.select_set(self._binary_search(self.start))self._selection_changed()return "break"else:# Unknown event, close the window and let it through.self.hide_window()return Nonedef keyrelease_event(self, event):if not self.is_active():returnif self.widget.index("insert") != \self.widget.index("%s+%dc" % (self.startindex, len(self.start))):# If we didn't catch an event which moved the insert, close windowself.hide_window()def is_active(self):return self.autocompletewindow is not Nonedef complete(self):self._change_start(self._complete_string(self.start))# The selection doesn't change.def hide_window(self):if not self.is_active():return# unbind eventsself.autocompletewindow.event_delete(HIDE_VIRTUAL_EVENT_NAME,HIDE_FOCUS_OUT_SEQUENCE)for seq in HIDE_SEQUENCES:self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)self.autocompletewindow.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideaid)self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hidewid)self.hideaid = Noneself.hidewid = Nonefor seq in KEYPRESS_SEQUENCES:self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)self.keypressid = Noneself.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)self.keyreleaseid = Noneself.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)self.listupdateid = Noneif self.winconfigid:self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)self.winconfigid = None# Re-focusOn frame.text (See issue #15786)self.widget.focus_set()# destroy widgetsself.scrollbar.destroy()self.scrollbar = Noneself.listbox.destroy()self.listbox = Noneself.autocompletewindow.destroy()self.autocompletewindow = Noneif __name__ == '__main__':from unittest import mainmain('idlelib.idle_test.test_autocomplete_w', verbosity=2, exit=False)# TODO: autocomplete/w htest here
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。