mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
further development of highlighting and editor preference handling,
plus misc fixes cleanups for new config system
This commit is contained in:
parent
767a7ee9ad
commit
ad4f532f65
5 changed files with 155 additions and 66 deletions
|
@ -1,56 +1,82 @@
|
||||||
# IDLE reads several config files to determine user preferences. This
|
# IDLE reads several config files to determine user preferences. This
|
||||||
# file is the default config file for idle highlight theme settings.
|
# file is the default config file for idle highlight theme settings.
|
||||||
|
|
||||||
[IDLE Classic Old - plain fonts]
|
[IDLE Classic]
|
||||||
normal-foreground= black
|
normal-foreground= #000000
|
||||||
normal-background= white
|
normal-background= #ffffff
|
||||||
normal-fontStyle= normal
|
normal-fontStyle= normal
|
||||||
keyword-foreground= #ff7700
|
keyword-foreground= #ff7700
|
||||||
|
keyword-background= #ffffff
|
||||||
keyword-fontStyle= normal
|
keyword-fontStyle= normal
|
||||||
comment-foreground= #dd0000
|
comment-foreground= #dd0000
|
||||||
|
comment-background= #ffffff
|
||||||
comment-fontStyle= normal
|
comment-fontStyle= normal
|
||||||
string-foreground= #00aa00
|
string-foreground= #00aa00
|
||||||
|
string-background= #ffffff
|
||||||
string-fontStyle= normal
|
string-fontStyle= normal
|
||||||
definition-foreground= #0000ff
|
definition-foreground= #0000ff
|
||||||
|
definition-background= #ffffff
|
||||||
definition-fontStyle= normal
|
definition-fontStyle= normal
|
||||||
hilite-foreground= #000068
|
hilite-foreground= #ffffff
|
||||||
hilite-background= #006868
|
hilite-background= gray
|
||||||
hilite-fontStyle= normal
|
hilite-fontStyle= normal
|
||||||
break-foreground= #ff7777
|
break-foreground= #ff7777
|
||||||
|
break-background= #ffffff
|
||||||
break-fontStyle= normal
|
break-fontStyle= normal
|
||||||
hit-background= #000000
|
|
||||||
hit-foreground= #ffffff
|
hit-foreground= #ffffff
|
||||||
|
hit-background= #000000
|
||||||
hit-fontStyle= normal
|
hit-fontStyle= normal
|
||||||
cursor-foreround= black
|
error-foreground= #000000
|
||||||
error-background= #ff7777
|
error-background= #ff7777
|
||||||
|
#cursor (only foreground can be set)
|
||||||
|
cursor-foreground= black
|
||||||
#shell window
|
#shell window
|
||||||
stdout-foreground= blue
|
stdout-foreground= blue
|
||||||
|
stdout-background= #ffffff
|
||||||
stdout-fontStyle= normal
|
stdout-fontStyle= normal
|
||||||
stderr-foreground= red
|
stderr-foreground= red
|
||||||
|
stderr-background= #ffffff
|
||||||
stderr-fontStyle= normal
|
stderr-fontStyle= normal
|
||||||
console-foreground= #770000
|
console-foreground= #770000
|
||||||
|
console-background= #ffffff
|
||||||
console-fontStyle= normal
|
console-fontStyle= normal
|
||||||
|
|
||||||
[IDLE Classic New]
|
[IDLE New]
|
||||||
normal-foreground= black
|
bold-foreground= #000000
|
||||||
normal-background= white
|
bold-background= #ffffff
|
||||||
normal-fontStyle= normal
|
bold-fontStyle= bold
|
||||||
keyword-foreground= #ff7700
|
keyword-foreground= #ff7700
|
||||||
|
keyword-background= #ffffff
|
||||||
keyword-fontStyle= bold
|
keyword-fontStyle= bold
|
||||||
comment-foreground= #dd0000
|
comment-foreground= #dd0000
|
||||||
comment-fontStyle= italic
|
comment-background= #ffffff
|
||||||
|
comment-fontStyle= bold
|
||||||
string-foreground= #00aa00
|
string-foreground= #00aa00
|
||||||
string-fontStyle= normal
|
string-background= #ffffff
|
||||||
|
string-fontStyle= bold
|
||||||
definition-foreground= #0000ff
|
definition-foreground= #0000ff
|
||||||
|
definition-background= #ffffff
|
||||||
definition-fontStyle= bold
|
definition-fontStyle= bold
|
||||||
hilite-foreground= #000068
|
hilite-foreground= #ffffff
|
||||||
hilite-background= #006868
|
hilite-background= gray
|
||||||
|
hilite-fontStyle= bold
|
||||||
break-foreground= #ff7777
|
break-foreground= #ff7777
|
||||||
hit-background= #000000
|
break-background= #ffffff
|
||||||
|
break-fontStyle= bold
|
||||||
hit-foreground= #ffffff
|
hit-foreground= #ffffff
|
||||||
cursor-foreground= black
|
hit-background= #000000
|
||||||
|
hit-fontStyle= bold
|
||||||
|
error-foreground= #000000
|
||||||
error-background= #ff7777
|
error-background= #ff7777
|
||||||
|
#cursor (only foreground can be set)
|
||||||
|
cursor-foreground= black
|
||||||
#shell window
|
#shell window
|
||||||
stdout-foreground= blue
|
stdout-foreground= blue
|
||||||
|
stdout-background= #ffffff
|
||||||
|
stdout-fontStyle= bold
|
||||||
stderr-foreground= red
|
stderr-foreground= red
|
||||||
|
stderr-background= #ffffff
|
||||||
|
stderr-fontStyle= bold
|
||||||
console-foreground= #770000
|
console-foreground= #770000
|
||||||
|
console-background= #ffffff
|
||||||
|
console-fontStyle= bold
|
||||||
|
|
|
@ -48,7 +48,7 @@ python="_Python Documentation",""
|
||||||
[EditorWindow]
|
[EditorWindow]
|
||||||
editor-on-startup= 0
|
editor-on-startup= 0
|
||||||
width= 80
|
width= 80
|
||||||
height= 24
|
height= 30
|
||||||
font= courier
|
font= courier
|
||||||
font-size= 12
|
font-size= 12
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ tab-cols= 4
|
||||||
|
|
||||||
[Theme]
|
[Theme]
|
||||||
default= 1
|
default= 1
|
||||||
name= IDLE Classic New
|
name= IDLE Classic
|
||||||
|
|
||||||
[Keys]
|
[Keys]
|
||||||
default= 1
|
default= 1
|
||||||
|
|
|
@ -49,7 +49,7 @@ cursor-background= black
|
||||||
|
|
||||||
[FormatParagraph]
|
[FormatParagraph]
|
||||||
|
|
||||||
[ZoomHeight]
|
#[ZoomHeight]
|
||||||
|
|
||||||
#[ScriptBinding] # disabled in favor of ExecBinding
|
#[ScriptBinding] # disabled in favor of ExecBinding
|
||||||
|
|
||||||
|
|
|
@ -22,18 +22,18 @@ class ConfigDialog(Toplevel):
|
||||||
#The second value is the display name list sort index.
|
#The second value is the display name list sort index.
|
||||||
#The third value indicates whether the element can have a foreground
|
#The third value indicates whether the element can have a foreground
|
||||||
#or background colour or both.
|
#or background colour or both.
|
||||||
self.themeElements={'Normal Text':('normal','00','both'),
|
self.themeElements={'Normal Text':('normal','00'),
|
||||||
'Python Keywords':('keyword','01','both'),
|
'Python Keywords':('keyword','01'),
|
||||||
'Python Definitions':('definition','02','both'),
|
'Python Definitions':('definition','02'),
|
||||||
'Python Comments':('comment','03','both'),
|
'Python Comments':('comment','03'),
|
||||||
'Python Strings':('string','04','both'),
|
'Python Strings':('string','04'),
|
||||||
'Selected Text':('hilite','05','both'),
|
'Selected Text':('hilite','05'),
|
||||||
'Found Text':('hit','06','both'),
|
'Found Text':('hit','06'),
|
||||||
'Cursor':('cursor','07','fg'),
|
'Cursor':('cursor','07'),
|
||||||
'Error Background':('error','08','bg'),
|
'Error Text':('error','08'),
|
||||||
'Shell Foreground':('console','09','fg'),
|
'Shell Normal Text':('console','09'),
|
||||||
'Shell Stdout Foreground':('stdout','10','fg'),
|
'Shell Stdout Text':('stdout','10'),
|
||||||
'Shell Stderr Foreground':('stderr','11','fg')}
|
'Shell Stderr Text':('stderr','11')}
|
||||||
self.CreateWidgets()
|
self.CreateWidgets()
|
||||||
self.resizable(height=FALSE,width=FALSE)
|
self.resizable(height=FALSE,width=FALSE)
|
||||||
self.transient(parent)
|
self.transient(parent)
|
||||||
|
@ -115,19 +115,14 @@ class ConfigDialog(Toplevel):
|
||||||
self.SetHighlightTarget()
|
self.SetHighlightTarget()
|
||||||
|
|
||||||
def SetHighlightTarget(self):
|
def SetHighlightTarget(self):
|
||||||
colourPlane=self.themeElements[self.highlightTarget.get()][2]
|
if self.highlightTarget.get()=='Cursor': #bg not possible
|
||||||
if colourPlane == 'bg':
|
self.radioFg.config(state=DISABLED)
|
||||||
self.radioFg.config(state=DISABLED)
|
self.radioBg.config(state=DISABLED)
|
||||||
self.radioBg.config(state=DISABLED)
|
self.fgHilite.set(1)
|
||||||
self.fgHilite.set(0)
|
else: #both fg and bg can be set
|
||||||
elif colourPlane == 'fg':
|
|
||||||
self.radioFg.config(state=DISABLED)
|
self.radioFg.config(state=DISABLED)
|
||||||
self.radioBg.config(state=DISABLED)
|
self.radioBg.config(state=DISABLED)
|
||||||
self.fgHilite.set(1)
|
self.fgHilite.set(1)
|
||||||
elif colourPlane == 'both':
|
|
||||||
self.radioFg.config(state=NORMAL)
|
|
||||||
self.radioBg.config(state=NORMAL)
|
|
||||||
self.fgHilite.set(1) #default to setting foreground attribute
|
|
||||||
self.SetColourSample()
|
self.SetColourSample()
|
||||||
|
|
||||||
def SetColourSampleBinding(self,*args):
|
def SetColourSampleBinding(self,*args):
|
||||||
|
@ -144,6 +139,7 @@ class ConfigDialog(Toplevel):
|
||||||
def CreateWidgets(self):
|
def CreateWidgets(self):
|
||||||
self.tabPages = TabPageSet(self,
|
self.tabPages = TabPageSet(self,
|
||||||
pageNames=['Fonts/Tabs','Highlighting','Keys','General'])
|
pageNames=['Fonts/Tabs','Highlighting','Keys','General'])
|
||||||
|
self.tabPages.ChangePage()#activates default (first) page
|
||||||
frameActionButtons = Frame(self)
|
frameActionButtons = Frame(self)
|
||||||
#action buttons
|
#action buttons
|
||||||
self.buttonHelp = Button(frameActionButtons,text='Help',
|
self.buttonHelp = Button(frameActionButtons,text='Help',
|
||||||
|
@ -498,9 +494,12 @@ class ConfigDialog(Toplevel):
|
||||||
theme=self.customTheme.get()
|
theme=self.customTheme.get()
|
||||||
for element in self.themeElements.keys():
|
for element in self.themeElements.keys():
|
||||||
colours=idleConf.GetHighlight(theme, self.themeElements[element][0])
|
colours=idleConf.GetHighlight(theme, self.themeElements[element][0])
|
||||||
|
if element=='Cursor': #cursor sample needs special painting
|
||||||
|
colours['background']=idleConf.GetHighlight(theme,
|
||||||
|
'normal-text', fgBg='bg')
|
||||||
apply(self.textHighlightSample.tag_config,
|
apply(self.textHighlightSample.tag_config,
|
||||||
(self.themeElements[element][0],),colours)
|
(self.themeElements[element][0],),colours)
|
||||||
|
|
||||||
def LoadFontCfg(self):
|
def LoadFontCfg(self):
|
||||||
##base editor font selection list
|
##base editor font selection list
|
||||||
fonts=list(tkFont.families(self))
|
fonts=list(tkFont.families(self))
|
||||||
|
@ -538,7 +537,7 @@ class ConfigDialog(Toplevel):
|
||||||
self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
|
self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
|
||||||
type='int',default=1))
|
type='int',default=1))
|
||||||
##currently set theme
|
##currently set theme
|
||||||
currentOption=idleConf.GetOption('main','Theme','name')
|
currentOption=idleConf.CurrentTheme()
|
||||||
##load available theme option menus
|
##load available theme option menus
|
||||||
if self.themeIsBuiltin.get(): #default theme selected
|
if self.themeIsBuiltin.get(): #default theme selected
|
||||||
itemList=idleConf.GetSectionList('default','highlight')
|
itemList=idleConf.GetSectionList('default','highlight')
|
||||||
|
@ -575,7 +574,7 @@ class ConfigDialog(Toplevel):
|
||||||
self.keysAreDefault.set(idleConf.GetOption('main','Keys','default',
|
self.keysAreDefault.set(idleConf.GetOption('main','Keys','default',
|
||||||
type='int',default=1))
|
type='int',default=1))
|
||||||
##currently set keys
|
##currently set keys
|
||||||
currentOption=idleConf.GetOption('main','Keys','name')
|
currentOption=idleConf.CurrentKeys()
|
||||||
##load available keyset option menus
|
##load available keyset option menus
|
||||||
if self.keysAreDefault.get(): #default theme selected
|
if self.keysAreDefault.get(): #default theme selected
|
||||||
itemList=idleConf.GetSectionList('default','keys')
|
itemList=idleConf.GetSectionList('default','keys')
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
##---------------------------------------------------------------------------##
|
|
||||||
##
|
|
||||||
## idle - configuration data handler, based on and replacing IdleConfig.py
|
|
||||||
## elguavas
|
|
||||||
##
|
|
||||||
##---------------------------------------------------------------------------##
|
|
||||||
"""
|
|
||||||
Provides access to stored idle configuration information
|
|
||||||
"""
|
"""
|
||||||
|
Provides access to stored idle configuration information.
|
||||||
|
|
||||||
|
Throughout this module there is an emphasis on returning useable defaults if
|
||||||
|
there is a problem returning a requested configuration value back to idle.
|
||||||
|
This is to allow idle to continue to function in spite of errors in the
|
||||||
|
retrieval of config information. When a default is returned instead of a
|
||||||
|
requested config value, a message is printed to stderr to aid in
|
||||||
|
configuration problem notification and resolution.
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from ConfigParser import ConfigParser, NoOptionError, NoSectionError
|
from ConfigParser import ConfigParser, NoOptionError, NoSectionError
|
||||||
|
@ -23,10 +23,11 @@ class IdleConfParser(ConfigParser):
|
||||||
self.file=cfgFile
|
self.file=cfgFile
|
||||||
ConfigParser.__init__(self,defaults=cfgDefaults)
|
ConfigParser.__init__(self,defaults=cfgDefaults)
|
||||||
|
|
||||||
def Get(self, section, option, default=None, type=None):
|
def Get(self, section, option, type=None): #,default=None)
|
||||||
"""
|
"""
|
||||||
Get an option value for given section/option or return default.
|
Get an option value for given section/option or return default.
|
||||||
If type is specified, return as type.
|
If type is specified, return as type.
|
||||||
|
If a default is returned a warning is printed to stderr.
|
||||||
"""
|
"""
|
||||||
if type=='bool':
|
if type=='bool':
|
||||||
getVal=self.getboolean
|
getVal=self.getboolean
|
||||||
|
@ -37,8 +38,14 @@ class IdleConfParser(ConfigParser):
|
||||||
if self.has_option(section,option):
|
if self.has_option(section,option):
|
||||||
#return getVal(section, option, raw, vars)
|
#return getVal(section, option, raw, vars)
|
||||||
return getVal(section, option)
|
return getVal(section, option)
|
||||||
else:
|
# #the following handled in IdleConf.GetOption instead
|
||||||
return default
|
# else:
|
||||||
|
# warning=('\n Warning: configHandler.py - IdleConfParser.Get -\n'+
|
||||||
|
# ' problem retrieving configration option '+`option`+'\n'+
|
||||||
|
# ' from section '+`section`+'.\n'+
|
||||||
|
# ' returning default value: '+`default`+'\n')
|
||||||
|
# sys.stderr.write(warning)
|
||||||
|
# return default
|
||||||
|
|
||||||
def GetOptionList(self,section):
|
def GetOptionList(self,section):
|
||||||
"""
|
"""
|
||||||
|
@ -75,10 +82,10 @@ class IdleConf:
|
||||||
(idle install dir)/config-highlight.def
|
(idle install dir)/config-highlight.def
|
||||||
(idle install dir)/config-keys.def
|
(idle install dir)/config-keys.def
|
||||||
user config files
|
user config files
|
||||||
(user home dir)/.idlerc/idle-main.cfg
|
(user home dir)/.idlerc/config-main.cfg
|
||||||
(user home dir)/.idlerc/idle-extensions.cfg
|
(user home dir)/.idlerc/config-extensions.cfg
|
||||||
(user home dir)/.idlerc/idle-highlight.cfg
|
(user home dir)/.idlerc/config-highlight.cfg
|
||||||
(user home dir)/.idlerc/idle-keys.cfg
|
(user home dir)/.idlerc/config-keys.cfg
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.defaultCfg={}
|
self.defaultCfg={}
|
||||||
|
@ -90,7 +97,7 @@ class IdleConf:
|
||||||
|
|
||||||
def CreateConfigHandlers(self):
|
def CreateConfigHandlers(self):
|
||||||
"""
|
"""
|
||||||
set up a dictionary config parsers for default and user
|
set up a dictionary of config parsers for default and user
|
||||||
configurations respectively
|
configurations respectively
|
||||||
"""
|
"""
|
||||||
#build idle install path
|
#build idle install path
|
||||||
|
@ -112,7 +119,7 @@ class IdleConf:
|
||||||
usrCfgFiles={}
|
usrCfgFiles={}
|
||||||
for cfgType in configTypes: #build config file names
|
for cfgType in configTypes: #build config file names
|
||||||
defCfgFiles[cfgType]=os.path.join(idledir,'config-'+cfgType+'.def')
|
defCfgFiles[cfgType]=os.path.join(idledir,'config-'+cfgType+'.def')
|
||||||
usrCfgFiles[cfgType]=os.path.join(userdir,'idle-'+cfgType+'.cfg')
|
usrCfgFiles[cfgType]=os.path.join(userdir,'config-'+cfgType+'.cfg')
|
||||||
for cfgType in configTypes: #create config parsers
|
for cfgType in configTypes: #create config parsers
|
||||||
self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
|
self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
|
||||||
self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
|
self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
|
||||||
|
@ -132,6 +139,11 @@ class IdleConf:
|
||||||
elif self.defaultCfg[configType].has_option(section,option):
|
elif self.defaultCfg[configType].has_option(section,option):
|
||||||
return self.defaultCfg[configType].Get(section, option, type=type)
|
return self.defaultCfg[configType].Get(section, option, type=type)
|
||||||
else:
|
else:
|
||||||
|
warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
|
||||||
|
' problem retrieving configration option '+`option`+'\n'+
|
||||||
|
' from section '+`section`+'.\n'+
|
||||||
|
' returning default value: '+`default`+'\n')
|
||||||
|
sys.stderr.write(warning)
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def GetSectionList(self, configSet, configType):
|
def GetSectionList(self, configSet, configType):
|
||||||
|
@ -152,7 +164,13 @@ class IdleConf:
|
||||||
|
|
||||||
return cfgParser.sections()
|
return cfgParser.sections()
|
||||||
|
|
||||||
def GetHighlight(self, theme, element):
|
def GetHighlight(self, theme, element, fgBg=None):
|
||||||
|
"""
|
||||||
|
return individual highlighting theme elements.
|
||||||
|
fgBg - string ('fg'or'bg') or None, if None return a dictionary
|
||||||
|
containing fg and bg colours (appropriate for passing to Tkinter in,
|
||||||
|
e.g., a tag_config call), otherwise fg or bg colour only as specified.
|
||||||
|
"""
|
||||||
#get some fallback defaults
|
#get some fallback defaults
|
||||||
defaultFg=self.GetOption('highlight', theme, 'normal' + "-foreground",
|
defaultFg=self.GetOption('highlight', theme, 'normal' + "-foreground",
|
||||||
default='#000000')
|
default='#000000')
|
||||||
|
@ -160,12 +178,25 @@ class IdleConf:
|
||||||
default='#ffffff')
|
default='#ffffff')
|
||||||
#try for requested element colours
|
#try for requested element colours
|
||||||
fore = self.GetOption('highlight', theme, element + "-foreground")
|
fore = self.GetOption('highlight', theme, element + "-foreground")
|
||||||
back = self.GetOption('highlight', theme, element + "-background")
|
back = None
|
||||||
|
if element == 'cursor': #there is no config value for cursor bg
|
||||||
|
back = None
|
||||||
|
else:
|
||||||
|
back = self.GetOption('highlight', theme, element + "-background")
|
||||||
#fall back if required
|
#fall back if required
|
||||||
if not fore: fore=defaultFg
|
if not fore: fore=defaultFg
|
||||||
if not back: back=defaultBg
|
if not back: back=defaultBg
|
||||||
return {"foreground": fore,
|
highlight={"foreground": fore,"background": back}
|
||||||
"background": back}
|
if not fgBg: #return dict of both colours
|
||||||
|
return highlight
|
||||||
|
else: #return specified colour only
|
||||||
|
if fgBg == 'fg':
|
||||||
|
return highlight["foreground"]
|
||||||
|
if fgBg == 'bg':
|
||||||
|
return highlight["background"]
|
||||||
|
else:
|
||||||
|
raise 'Invalid fgBg specified'
|
||||||
|
|
||||||
|
|
||||||
def GetTheme(self, name=None):
|
def GetTheme(self, name=None):
|
||||||
"""
|
"""
|
||||||
|
@ -174,6 +205,39 @@ class IdleConf:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def CurrentTheme(self):
|
||||||
|
"""
|
||||||
|
Returns the name of the currently active theme
|
||||||
|
"""
|
||||||
|
return self.GetOption('main','Theme','name')
|
||||||
|
|
||||||
|
|
||||||
|
def CurrentKeys(self):
|
||||||
|
"""
|
||||||
|
Returns the name of the currently active theme
|
||||||
|
"""
|
||||||
|
return self.GetOption('main','Keys','name')
|
||||||
|
|
||||||
|
def GetExtensions(self, activeOnly=1):
|
||||||
|
"""
|
||||||
|
Gets a list of all idle extensions declared in the config files.
|
||||||
|
activeOnly - boolean, if true only return active (enabled) extensions
|
||||||
|
"""
|
||||||
|
extns=self.GetSectionList('default','extensions')
|
||||||
|
userExtns=self.GetSectionList('user','extensions')
|
||||||
|
for extn in userExtns:
|
||||||
|
if extn not in extns: #user has added own extension
|
||||||
|
extns.append(extn)
|
||||||
|
if activeOnly:
|
||||||
|
activeExtns=[]
|
||||||
|
for extn in extns:
|
||||||
|
if self.GetOption('extensions',extn,'enable',default=1,type='bool'):
|
||||||
|
#the extension is enabled
|
||||||
|
activeExtns.append(extn)
|
||||||
|
return activeExtns
|
||||||
|
else:
|
||||||
|
return extns
|
||||||
|
|
||||||
def GetKeys(self, keySetName=None):
|
def GetKeys(self, keySetName=None):
|
||||||
"""
|
"""
|
||||||
returns the requested keybindings, with fallbacks if required.
|
returns the requested keybindings, with fallbacks if required.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue