17db96d56Sopenharmony_ci"""idlelib.config -- Manage IDLE configuration information. 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciThe comments at the beginning of config-main.def describe the 47db96d56Sopenharmony_ciconfiguration files and the design implemented to update user 57db96d56Sopenharmony_ciconfiguration information. In particular, user configuration choices 67db96d56Sopenharmony_ciwhich duplicate the defaults will be removed from the user's 77db96d56Sopenharmony_ciconfiguration files, and if a user file becomes empty, it will be 87db96d56Sopenharmony_cideleted. 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ciThe configuration database maps options to values. Conceptually, the 117db96d56Sopenharmony_cidatabase keys are tuples (config-type, section, item). As implemented, 127db96d56Sopenharmony_cithere are separate dicts for default and user values. Each has 137db96d56Sopenharmony_ciconfig-type keys 'main', 'extensions', 'highlight', and 'keys'. The 147db96d56Sopenharmony_civalue for each key is a ConfigParser instance that maps section and item 157db96d56Sopenharmony_cito values. For 'main' and 'extensions', user values override 167db96d56Sopenharmony_cidefault values. For 'highlight' and 'keys', user sections augment the 177db96d56Sopenharmony_cidefault sections (and must, therefore, have distinct names). 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ciThroughout this module there is an emphasis on returning usable defaults 207db96d56Sopenharmony_ciwhen a problem occurs in returning a requested configuration value back to 217db96d56Sopenharmony_ciidle. This is to allow IDLE to continue to function in spite of errors in 227db96d56Sopenharmony_cithe retrieval of config information. When a default is returned instead of 237db96d56Sopenharmony_cia requested config value, a message is printed to stderr to aid in 247db96d56Sopenharmony_ciconfiguration problem notification and resolution. 257db96d56Sopenharmony_ci""" 267db96d56Sopenharmony_ci# TODOs added Oct 2014, tjr 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_cifrom configparser import ConfigParser 297db96d56Sopenharmony_ciimport os 307db96d56Sopenharmony_ciimport sys 317db96d56Sopenharmony_ci 327db96d56Sopenharmony_cifrom tkinter.font import Font 337db96d56Sopenharmony_ciimport idlelib 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ciclass InvalidConfigType(Exception): pass 367db96d56Sopenharmony_ciclass InvalidConfigSet(Exception): pass 377db96d56Sopenharmony_ciclass InvalidTheme(Exception): pass 387db96d56Sopenharmony_ci 397db96d56Sopenharmony_ciclass IdleConfParser(ConfigParser): 407db96d56Sopenharmony_ci """ 417db96d56Sopenharmony_ci A ConfigParser specialised for idle configuration file handling 427db96d56Sopenharmony_ci """ 437db96d56Sopenharmony_ci def __init__(self, cfgFile, cfgDefaults=None): 447db96d56Sopenharmony_ci """ 457db96d56Sopenharmony_ci cfgFile - string, fully specified configuration file name 467db96d56Sopenharmony_ci """ 477db96d56Sopenharmony_ci self.file = cfgFile # This is currently '' when testing. 487db96d56Sopenharmony_ci ConfigParser.__init__(self, defaults=cfgDefaults, strict=False) 497db96d56Sopenharmony_ci 507db96d56Sopenharmony_ci def Get(self, section, option, type=None, default=None, raw=False): 517db96d56Sopenharmony_ci """ 527db96d56Sopenharmony_ci Get an option value for given section/option or return default. 537db96d56Sopenharmony_ci If type is specified, return as type. 547db96d56Sopenharmony_ci """ 557db96d56Sopenharmony_ci # TODO Use default as fallback, at least if not None 567db96d56Sopenharmony_ci # Should also print Warning(file, section, option). 577db96d56Sopenharmony_ci # Currently may raise ValueError 587db96d56Sopenharmony_ci if not self.has_option(section, option): 597db96d56Sopenharmony_ci return default 607db96d56Sopenharmony_ci if type == 'bool': 617db96d56Sopenharmony_ci return self.getboolean(section, option) 627db96d56Sopenharmony_ci elif type == 'int': 637db96d56Sopenharmony_ci return self.getint(section, option) 647db96d56Sopenharmony_ci else: 657db96d56Sopenharmony_ci return self.get(section, option, raw=raw) 667db96d56Sopenharmony_ci 677db96d56Sopenharmony_ci def GetOptionList(self, section): 687db96d56Sopenharmony_ci "Return a list of options for given section, else []." 697db96d56Sopenharmony_ci if self.has_section(section): 707db96d56Sopenharmony_ci return self.options(section) 717db96d56Sopenharmony_ci else: #return a default value 727db96d56Sopenharmony_ci return [] 737db96d56Sopenharmony_ci 747db96d56Sopenharmony_ci def Load(self): 757db96d56Sopenharmony_ci "Load the configuration file from disk." 767db96d56Sopenharmony_ci if self.file: 777db96d56Sopenharmony_ci self.read(self.file) 787db96d56Sopenharmony_ci 797db96d56Sopenharmony_ciclass IdleUserConfParser(IdleConfParser): 807db96d56Sopenharmony_ci """ 817db96d56Sopenharmony_ci IdleConfigParser specialised for user configuration handling. 827db96d56Sopenharmony_ci """ 837db96d56Sopenharmony_ci 847db96d56Sopenharmony_ci def SetOption(self, section, option, value): 857db96d56Sopenharmony_ci """Return True if option is added or changed to value, else False. 867db96d56Sopenharmony_ci 877db96d56Sopenharmony_ci Add section if required. False means option already had value. 887db96d56Sopenharmony_ci """ 897db96d56Sopenharmony_ci if self.has_option(section, option): 907db96d56Sopenharmony_ci if self.get(section, option) == value: 917db96d56Sopenharmony_ci return False 927db96d56Sopenharmony_ci else: 937db96d56Sopenharmony_ci self.set(section, option, value) 947db96d56Sopenharmony_ci return True 957db96d56Sopenharmony_ci else: 967db96d56Sopenharmony_ci if not self.has_section(section): 977db96d56Sopenharmony_ci self.add_section(section) 987db96d56Sopenharmony_ci self.set(section, option, value) 997db96d56Sopenharmony_ci return True 1007db96d56Sopenharmony_ci 1017db96d56Sopenharmony_ci def RemoveOption(self, section, option): 1027db96d56Sopenharmony_ci """Return True if option is removed from section, else False. 1037db96d56Sopenharmony_ci 1047db96d56Sopenharmony_ci False if either section does not exist or did not have option. 1057db96d56Sopenharmony_ci """ 1067db96d56Sopenharmony_ci if self.has_section(section): 1077db96d56Sopenharmony_ci return self.remove_option(section, option) 1087db96d56Sopenharmony_ci return False 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci def AddSection(self, section): 1117db96d56Sopenharmony_ci "If section doesn't exist, add it." 1127db96d56Sopenharmony_ci if not self.has_section(section): 1137db96d56Sopenharmony_ci self.add_section(section) 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci def RemoveEmptySections(self): 1167db96d56Sopenharmony_ci "Remove any sections that have no options." 1177db96d56Sopenharmony_ci for section in self.sections(): 1187db96d56Sopenharmony_ci if not self.GetOptionList(section): 1197db96d56Sopenharmony_ci self.remove_section(section) 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci def IsEmpty(self): 1227db96d56Sopenharmony_ci "Return True if no sections after removing empty sections." 1237db96d56Sopenharmony_ci self.RemoveEmptySections() 1247db96d56Sopenharmony_ci return not self.sections() 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci def Save(self): 1277db96d56Sopenharmony_ci """Update user configuration file. 1287db96d56Sopenharmony_ci 1297db96d56Sopenharmony_ci If self not empty after removing empty sections, write the file 1307db96d56Sopenharmony_ci to disk. Otherwise, remove the file from disk if it exists. 1317db96d56Sopenharmony_ci """ 1327db96d56Sopenharmony_ci fname = self.file 1337db96d56Sopenharmony_ci if fname and fname[0] != '#': 1347db96d56Sopenharmony_ci if not self.IsEmpty(): 1357db96d56Sopenharmony_ci try: 1367db96d56Sopenharmony_ci cfgFile = open(fname, 'w') 1377db96d56Sopenharmony_ci except OSError: 1387db96d56Sopenharmony_ci os.unlink(fname) 1397db96d56Sopenharmony_ci cfgFile = open(fname, 'w') 1407db96d56Sopenharmony_ci with cfgFile: 1417db96d56Sopenharmony_ci self.write(cfgFile) 1427db96d56Sopenharmony_ci elif os.path.exists(self.file): 1437db96d56Sopenharmony_ci os.remove(self.file) 1447db96d56Sopenharmony_ci 1457db96d56Sopenharmony_ciclass IdleConf: 1467db96d56Sopenharmony_ci """Hold config parsers for all idle config files in singleton instance. 1477db96d56Sopenharmony_ci 1487db96d56Sopenharmony_ci Default config files, self.defaultCfg -- 1497db96d56Sopenharmony_ci for config_type in self.config_types: 1507db96d56Sopenharmony_ci (idle install dir)/config-{config-type}.def 1517db96d56Sopenharmony_ci 1527db96d56Sopenharmony_ci User config files, self.userCfg -- 1537db96d56Sopenharmony_ci for config_type in self.config_types: 1547db96d56Sopenharmony_ci (user home dir)/.idlerc/config-{config-type}.cfg 1557db96d56Sopenharmony_ci """ 1567db96d56Sopenharmony_ci def __init__(self, _utest=False): 1577db96d56Sopenharmony_ci self.config_types = ('main', 'highlight', 'keys', 'extensions') 1587db96d56Sopenharmony_ci self.defaultCfg = {} 1597db96d56Sopenharmony_ci self.userCfg = {} 1607db96d56Sopenharmony_ci self.cfg = {} # TODO use to select userCfg vs defaultCfg 1617db96d56Sopenharmony_ci # self.blink_off_time = <first editor text>['insertofftime'] 1627db96d56Sopenharmony_ci # See https:/bugs.python.org/issue4630, msg356516. 1637db96d56Sopenharmony_ci 1647db96d56Sopenharmony_ci if not _utest: 1657db96d56Sopenharmony_ci self.CreateConfigHandlers() 1667db96d56Sopenharmony_ci self.LoadCfgFiles() 1677db96d56Sopenharmony_ci 1687db96d56Sopenharmony_ci def CreateConfigHandlers(self): 1697db96d56Sopenharmony_ci "Populate default and user config parser dictionaries." 1707db96d56Sopenharmony_ci idledir = os.path.dirname(__file__) 1717db96d56Sopenharmony_ci self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir() 1727db96d56Sopenharmony_ci for cfg_type in self.config_types: 1737db96d56Sopenharmony_ci self.defaultCfg[cfg_type] = IdleConfParser( 1747db96d56Sopenharmony_ci os.path.join(idledir, f'config-{cfg_type}.def')) 1757db96d56Sopenharmony_ci self.userCfg[cfg_type] = IdleUserConfParser( 1767db96d56Sopenharmony_ci os.path.join(userdir or '#', f'config-{cfg_type}.cfg')) 1777db96d56Sopenharmony_ci 1787db96d56Sopenharmony_ci def GetUserCfgDir(self): 1797db96d56Sopenharmony_ci """Return a filesystem directory for storing user config files. 1807db96d56Sopenharmony_ci 1817db96d56Sopenharmony_ci Creates it if required. 1827db96d56Sopenharmony_ci """ 1837db96d56Sopenharmony_ci cfgDir = '.idlerc' 1847db96d56Sopenharmony_ci userDir = os.path.expanduser('~') 1857db96d56Sopenharmony_ci if userDir != '~': # expanduser() found user home dir 1867db96d56Sopenharmony_ci if not os.path.exists(userDir): 1877db96d56Sopenharmony_ci if not idlelib.testing: 1887db96d56Sopenharmony_ci warn = ('\n Warning: os.path.expanduser("~") points to\n ' + 1897db96d56Sopenharmony_ci userDir + ',\n but the path does not exist.') 1907db96d56Sopenharmony_ci try: 1917db96d56Sopenharmony_ci print(warn, file=sys.stderr) 1927db96d56Sopenharmony_ci except OSError: 1937db96d56Sopenharmony_ci pass 1947db96d56Sopenharmony_ci userDir = '~' 1957db96d56Sopenharmony_ci if userDir == "~": # still no path to home! 1967db96d56Sopenharmony_ci # traditionally IDLE has defaulted to os.getcwd(), is this adequate? 1977db96d56Sopenharmony_ci userDir = os.getcwd() 1987db96d56Sopenharmony_ci userDir = os.path.join(userDir, cfgDir) 1997db96d56Sopenharmony_ci if not os.path.exists(userDir): 2007db96d56Sopenharmony_ci try: 2017db96d56Sopenharmony_ci os.mkdir(userDir) 2027db96d56Sopenharmony_ci except OSError: 2037db96d56Sopenharmony_ci if not idlelib.testing: 2047db96d56Sopenharmony_ci warn = ('\n Warning: unable to create user config directory\n' + 2057db96d56Sopenharmony_ci userDir + '\n Check path and permissions.\n Exiting!\n') 2067db96d56Sopenharmony_ci try: 2077db96d56Sopenharmony_ci print(warn, file=sys.stderr) 2087db96d56Sopenharmony_ci except OSError: 2097db96d56Sopenharmony_ci pass 2107db96d56Sopenharmony_ci raise SystemExit 2117db96d56Sopenharmony_ci # TODO continue without userDIr instead of exit 2127db96d56Sopenharmony_ci return userDir 2137db96d56Sopenharmony_ci 2147db96d56Sopenharmony_ci def GetOption(self, configType, section, option, default=None, type=None, 2157db96d56Sopenharmony_ci warn_on_default=True, raw=False): 2167db96d56Sopenharmony_ci """Return a value for configType section option, or default. 2177db96d56Sopenharmony_ci 2187db96d56Sopenharmony_ci If type is not None, return a value of that type. Also pass raw 2197db96d56Sopenharmony_ci to the config parser. First try to return a valid value 2207db96d56Sopenharmony_ci (including type) from a user configuration. If that fails, try 2217db96d56Sopenharmony_ci the default configuration. If that fails, return default, with a 2227db96d56Sopenharmony_ci default of None. 2237db96d56Sopenharmony_ci 2247db96d56Sopenharmony_ci Warn if either user or default configurations have an invalid value. 2257db96d56Sopenharmony_ci Warn if default is returned and warn_on_default is True. 2267db96d56Sopenharmony_ci """ 2277db96d56Sopenharmony_ci try: 2287db96d56Sopenharmony_ci if self.userCfg[configType].has_option(section, option): 2297db96d56Sopenharmony_ci return self.userCfg[configType].Get(section, option, 2307db96d56Sopenharmony_ci type=type, raw=raw) 2317db96d56Sopenharmony_ci except ValueError: 2327db96d56Sopenharmony_ci warning = ('\n Warning: config.py - IdleConf.GetOption -\n' 2337db96d56Sopenharmony_ci ' invalid %r value for configuration option %r\n' 2347db96d56Sopenharmony_ci ' from section %r: %r' % 2357db96d56Sopenharmony_ci (type, option, section, 2367db96d56Sopenharmony_ci self.userCfg[configType].Get(section, option, raw=raw))) 2377db96d56Sopenharmony_ci _warn(warning, configType, section, option) 2387db96d56Sopenharmony_ci try: 2397db96d56Sopenharmony_ci if self.defaultCfg[configType].has_option(section,option): 2407db96d56Sopenharmony_ci return self.defaultCfg[configType].Get( 2417db96d56Sopenharmony_ci section, option, type=type, raw=raw) 2427db96d56Sopenharmony_ci except ValueError: 2437db96d56Sopenharmony_ci pass 2447db96d56Sopenharmony_ci #returning default, print warning 2457db96d56Sopenharmony_ci if warn_on_default: 2467db96d56Sopenharmony_ci warning = ('\n Warning: config.py - IdleConf.GetOption -\n' 2477db96d56Sopenharmony_ci ' problem retrieving configuration option %r\n' 2487db96d56Sopenharmony_ci ' from section %r.\n' 2497db96d56Sopenharmony_ci ' returning default value: %r' % 2507db96d56Sopenharmony_ci (option, section, default)) 2517db96d56Sopenharmony_ci _warn(warning, configType, section, option) 2527db96d56Sopenharmony_ci return default 2537db96d56Sopenharmony_ci 2547db96d56Sopenharmony_ci def SetOption(self, configType, section, option, value): 2557db96d56Sopenharmony_ci """Set section option to value in user config file.""" 2567db96d56Sopenharmony_ci self.userCfg[configType].SetOption(section, option, value) 2577db96d56Sopenharmony_ci 2587db96d56Sopenharmony_ci def GetSectionList(self, configSet, configType): 2597db96d56Sopenharmony_ci """Return sections for configSet configType configuration. 2607db96d56Sopenharmony_ci 2617db96d56Sopenharmony_ci configSet must be either 'user' or 'default' 2627db96d56Sopenharmony_ci configType must be in self.config_types. 2637db96d56Sopenharmony_ci """ 2647db96d56Sopenharmony_ci if not (configType in self.config_types): 2657db96d56Sopenharmony_ci raise InvalidConfigType('Invalid configType specified') 2667db96d56Sopenharmony_ci if configSet == 'user': 2677db96d56Sopenharmony_ci cfgParser = self.userCfg[configType] 2687db96d56Sopenharmony_ci elif configSet == 'default': 2697db96d56Sopenharmony_ci cfgParser=self.defaultCfg[configType] 2707db96d56Sopenharmony_ci else: 2717db96d56Sopenharmony_ci raise InvalidConfigSet('Invalid configSet specified') 2727db96d56Sopenharmony_ci return cfgParser.sections() 2737db96d56Sopenharmony_ci 2747db96d56Sopenharmony_ci def GetHighlight(self, theme, element): 2757db96d56Sopenharmony_ci """Return dict of theme element highlight colors. 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci The keys are 'foreground' and 'background'. The values are 2787db96d56Sopenharmony_ci tkinter color strings for configuring backgrounds and tags. 2797db96d56Sopenharmony_ci """ 2807db96d56Sopenharmony_ci cfg = ('default' if self.defaultCfg['highlight'].has_section(theme) 2817db96d56Sopenharmony_ci else 'user') 2827db96d56Sopenharmony_ci theme_dict = self.GetThemeDict(cfg, theme) 2837db96d56Sopenharmony_ci fore = theme_dict[element + '-foreground'] 2847db96d56Sopenharmony_ci if element == 'cursor': 2857db96d56Sopenharmony_ci element = 'normal' 2867db96d56Sopenharmony_ci back = theme_dict[element + '-background'] 2877db96d56Sopenharmony_ci return {"foreground": fore, "background": back} 2887db96d56Sopenharmony_ci 2897db96d56Sopenharmony_ci def GetThemeDict(self, type, themeName): 2907db96d56Sopenharmony_ci """Return {option:value} dict for elements in themeName. 2917db96d56Sopenharmony_ci 2927db96d56Sopenharmony_ci type - string, 'default' or 'user' theme type 2937db96d56Sopenharmony_ci themeName - string, theme name 2947db96d56Sopenharmony_ci Values are loaded over ultimate fallback defaults to guarantee 2957db96d56Sopenharmony_ci that all theme elements are present in a newly created theme. 2967db96d56Sopenharmony_ci """ 2977db96d56Sopenharmony_ci if type == 'user': 2987db96d56Sopenharmony_ci cfgParser = self.userCfg['highlight'] 2997db96d56Sopenharmony_ci elif type == 'default': 3007db96d56Sopenharmony_ci cfgParser = self.defaultCfg['highlight'] 3017db96d56Sopenharmony_ci else: 3027db96d56Sopenharmony_ci raise InvalidTheme('Invalid theme type specified') 3037db96d56Sopenharmony_ci # Provide foreground and background colors for each theme 3047db96d56Sopenharmony_ci # element (other than cursor) even though some values are not 3057db96d56Sopenharmony_ci # yet used by idle, to allow for their use in the future. 3067db96d56Sopenharmony_ci # Default values are generally black and white. 3077db96d56Sopenharmony_ci # TODO copy theme from a class attribute. 3087db96d56Sopenharmony_ci theme ={'normal-foreground':'#000000', 3097db96d56Sopenharmony_ci 'normal-background':'#ffffff', 3107db96d56Sopenharmony_ci 'keyword-foreground':'#000000', 3117db96d56Sopenharmony_ci 'keyword-background':'#ffffff', 3127db96d56Sopenharmony_ci 'builtin-foreground':'#000000', 3137db96d56Sopenharmony_ci 'builtin-background':'#ffffff', 3147db96d56Sopenharmony_ci 'comment-foreground':'#000000', 3157db96d56Sopenharmony_ci 'comment-background':'#ffffff', 3167db96d56Sopenharmony_ci 'string-foreground':'#000000', 3177db96d56Sopenharmony_ci 'string-background':'#ffffff', 3187db96d56Sopenharmony_ci 'definition-foreground':'#000000', 3197db96d56Sopenharmony_ci 'definition-background':'#ffffff', 3207db96d56Sopenharmony_ci 'hilite-foreground':'#000000', 3217db96d56Sopenharmony_ci 'hilite-background':'gray', 3227db96d56Sopenharmony_ci 'break-foreground':'#ffffff', 3237db96d56Sopenharmony_ci 'break-background':'#000000', 3247db96d56Sopenharmony_ci 'hit-foreground':'#ffffff', 3257db96d56Sopenharmony_ci 'hit-background':'#000000', 3267db96d56Sopenharmony_ci 'error-foreground':'#ffffff', 3277db96d56Sopenharmony_ci 'error-background':'#000000', 3287db96d56Sopenharmony_ci 'context-foreground':'#000000', 3297db96d56Sopenharmony_ci 'context-background':'#ffffff', 3307db96d56Sopenharmony_ci 'linenumber-foreground':'#000000', 3317db96d56Sopenharmony_ci 'linenumber-background':'#ffffff', 3327db96d56Sopenharmony_ci #cursor (only foreground can be set) 3337db96d56Sopenharmony_ci 'cursor-foreground':'#000000', 3347db96d56Sopenharmony_ci #shell window 3357db96d56Sopenharmony_ci 'stdout-foreground':'#000000', 3367db96d56Sopenharmony_ci 'stdout-background':'#ffffff', 3377db96d56Sopenharmony_ci 'stderr-foreground':'#000000', 3387db96d56Sopenharmony_ci 'stderr-background':'#ffffff', 3397db96d56Sopenharmony_ci 'console-foreground':'#000000', 3407db96d56Sopenharmony_ci 'console-background':'#ffffff', 3417db96d56Sopenharmony_ci } 3427db96d56Sopenharmony_ci for element in theme: 3437db96d56Sopenharmony_ci if not (cfgParser.has_option(themeName, element) or 3447db96d56Sopenharmony_ci # Skip warning for new elements. 3457db96d56Sopenharmony_ci element.startswith(('context-', 'linenumber-'))): 3467db96d56Sopenharmony_ci # Print warning that will return a default color 3477db96d56Sopenharmony_ci warning = ('\n Warning: config.IdleConf.GetThemeDict' 3487db96d56Sopenharmony_ci ' -\n problem retrieving theme element %r' 3497db96d56Sopenharmony_ci '\n from theme %r.\n' 3507db96d56Sopenharmony_ci ' returning default color: %r' % 3517db96d56Sopenharmony_ci (element, themeName, theme[element])) 3527db96d56Sopenharmony_ci _warn(warning, 'highlight', themeName, element) 3537db96d56Sopenharmony_ci theme[element] = cfgParser.Get( 3547db96d56Sopenharmony_ci themeName, element, default=theme[element]) 3557db96d56Sopenharmony_ci return theme 3567db96d56Sopenharmony_ci 3577db96d56Sopenharmony_ci def CurrentTheme(self): 3587db96d56Sopenharmony_ci "Return the name of the currently active text color theme." 3597db96d56Sopenharmony_ci return self.current_colors_and_keys('Theme') 3607db96d56Sopenharmony_ci 3617db96d56Sopenharmony_ci def CurrentKeys(self): 3627db96d56Sopenharmony_ci """Return the name of the currently active key set.""" 3637db96d56Sopenharmony_ci return self.current_colors_and_keys('Keys') 3647db96d56Sopenharmony_ci 3657db96d56Sopenharmony_ci def current_colors_and_keys(self, section): 3667db96d56Sopenharmony_ci """Return the currently active name for Theme or Keys section. 3677db96d56Sopenharmony_ci 3687db96d56Sopenharmony_ci idlelib.config-main.def ('default') includes these sections 3697db96d56Sopenharmony_ci 3707db96d56Sopenharmony_ci [Theme] 3717db96d56Sopenharmony_ci default= 1 3727db96d56Sopenharmony_ci name= IDLE Classic 3737db96d56Sopenharmony_ci name2= 3747db96d56Sopenharmony_ci 3757db96d56Sopenharmony_ci [Keys] 3767db96d56Sopenharmony_ci default= 1 3777db96d56Sopenharmony_ci name= 3787db96d56Sopenharmony_ci name2= 3797db96d56Sopenharmony_ci 3807db96d56Sopenharmony_ci Item 'name2', is used for built-in ('default') themes and keys 3817db96d56Sopenharmony_ci added after 2015 Oct 1 and 2016 July 1. This kludge is needed 3827db96d56Sopenharmony_ci because setting 'name' to a builtin not defined in older IDLEs 3837db96d56Sopenharmony_ci to display multiple error messages or quit. 3847db96d56Sopenharmony_ci See https://bugs.python.org/issue25313. 3857db96d56Sopenharmony_ci When default = True, 'name2' takes precedence over 'name', 3867db96d56Sopenharmony_ci while older IDLEs will just use name. When default = False, 3877db96d56Sopenharmony_ci 'name2' may still be set, but it is ignored. 3887db96d56Sopenharmony_ci """ 3897db96d56Sopenharmony_ci cfgname = 'highlight' if section == 'Theme' else 'keys' 3907db96d56Sopenharmony_ci default = self.GetOption('main', section, 'default', 3917db96d56Sopenharmony_ci type='bool', default=True) 3927db96d56Sopenharmony_ci name = '' 3937db96d56Sopenharmony_ci if default: 3947db96d56Sopenharmony_ci name = self.GetOption('main', section, 'name2', default='') 3957db96d56Sopenharmony_ci if not name: 3967db96d56Sopenharmony_ci name = self.GetOption('main', section, 'name', default='') 3977db96d56Sopenharmony_ci if name: 3987db96d56Sopenharmony_ci source = self.defaultCfg if default else self.userCfg 3997db96d56Sopenharmony_ci if source[cfgname].has_section(name): 4007db96d56Sopenharmony_ci return name 4017db96d56Sopenharmony_ci return "IDLE Classic" if section == 'Theme' else self.default_keys() 4027db96d56Sopenharmony_ci 4037db96d56Sopenharmony_ci @staticmethod 4047db96d56Sopenharmony_ci def default_keys(): 4057db96d56Sopenharmony_ci if sys.platform[:3] == 'win': 4067db96d56Sopenharmony_ci return 'IDLE Classic Windows' 4077db96d56Sopenharmony_ci elif sys.platform == 'darwin': 4087db96d56Sopenharmony_ci return 'IDLE Classic OSX' 4097db96d56Sopenharmony_ci else: 4107db96d56Sopenharmony_ci return 'IDLE Modern Unix' 4117db96d56Sopenharmony_ci 4127db96d56Sopenharmony_ci def GetExtensions(self, active_only=True, 4137db96d56Sopenharmony_ci editor_only=False, shell_only=False): 4147db96d56Sopenharmony_ci """Return extensions in default and user config-extensions files. 4157db96d56Sopenharmony_ci 4167db96d56Sopenharmony_ci If active_only True, only return active (enabled) extensions 4177db96d56Sopenharmony_ci and optionally only editor or shell extensions. 4187db96d56Sopenharmony_ci If active_only False, return all extensions. 4197db96d56Sopenharmony_ci """ 4207db96d56Sopenharmony_ci extns = self.RemoveKeyBindNames( 4217db96d56Sopenharmony_ci self.GetSectionList('default', 'extensions')) 4227db96d56Sopenharmony_ci userExtns = self.RemoveKeyBindNames( 4237db96d56Sopenharmony_ci self.GetSectionList('user', 'extensions')) 4247db96d56Sopenharmony_ci for extn in userExtns: 4257db96d56Sopenharmony_ci if extn not in extns: #user has added own extension 4267db96d56Sopenharmony_ci extns.append(extn) 4277db96d56Sopenharmony_ci for extn in ('AutoComplete','CodeContext', 4287db96d56Sopenharmony_ci 'FormatParagraph','ParenMatch'): 4297db96d56Sopenharmony_ci extns.remove(extn) 4307db96d56Sopenharmony_ci # specific exclusions because we are storing config for mainlined old 4317db96d56Sopenharmony_ci # extensions in config-extensions.def for backward compatibility 4327db96d56Sopenharmony_ci if active_only: 4337db96d56Sopenharmony_ci activeExtns = [] 4347db96d56Sopenharmony_ci for extn in extns: 4357db96d56Sopenharmony_ci if self.GetOption('extensions', extn, 'enable', default=True, 4367db96d56Sopenharmony_ci type='bool'): 4377db96d56Sopenharmony_ci #the extension is enabled 4387db96d56Sopenharmony_ci if editor_only or shell_only: # TODO both True contradict 4397db96d56Sopenharmony_ci if editor_only: 4407db96d56Sopenharmony_ci option = "enable_editor" 4417db96d56Sopenharmony_ci else: 4427db96d56Sopenharmony_ci option = "enable_shell" 4437db96d56Sopenharmony_ci if self.GetOption('extensions', extn,option, 4447db96d56Sopenharmony_ci default=True, type='bool', 4457db96d56Sopenharmony_ci warn_on_default=False): 4467db96d56Sopenharmony_ci activeExtns.append(extn) 4477db96d56Sopenharmony_ci else: 4487db96d56Sopenharmony_ci activeExtns.append(extn) 4497db96d56Sopenharmony_ci return activeExtns 4507db96d56Sopenharmony_ci else: 4517db96d56Sopenharmony_ci return extns 4527db96d56Sopenharmony_ci 4537db96d56Sopenharmony_ci def RemoveKeyBindNames(self, extnNameList): 4547db96d56Sopenharmony_ci "Return extnNameList with keybinding section names removed." 4557db96d56Sopenharmony_ci return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))] 4567db96d56Sopenharmony_ci 4577db96d56Sopenharmony_ci def GetExtnNameForEvent(self, virtualEvent): 4587db96d56Sopenharmony_ci """Return the name of the extension binding virtualEvent, or None. 4597db96d56Sopenharmony_ci 4607db96d56Sopenharmony_ci virtualEvent - string, name of the virtual event to test for, 4617db96d56Sopenharmony_ci without the enclosing '<< >>' 4627db96d56Sopenharmony_ci """ 4637db96d56Sopenharmony_ci extName = None 4647db96d56Sopenharmony_ci vEvent = '<<' + virtualEvent + '>>' 4657db96d56Sopenharmony_ci for extn in self.GetExtensions(active_only=0): 4667db96d56Sopenharmony_ci for event in self.GetExtensionKeys(extn): 4677db96d56Sopenharmony_ci if event == vEvent: 4687db96d56Sopenharmony_ci extName = extn # TODO return here? 4697db96d56Sopenharmony_ci return extName 4707db96d56Sopenharmony_ci 4717db96d56Sopenharmony_ci def GetExtensionKeys(self, extensionName): 4727db96d56Sopenharmony_ci """Return dict: {configurable extensionName event : active keybinding}. 4737db96d56Sopenharmony_ci 4747db96d56Sopenharmony_ci Events come from default config extension_cfgBindings section. 4757db96d56Sopenharmony_ci Keybindings come from GetCurrentKeySet() active key dict, 4767db96d56Sopenharmony_ci where previously used bindings are disabled. 4777db96d56Sopenharmony_ci """ 4787db96d56Sopenharmony_ci keysName = extensionName + '_cfgBindings' 4797db96d56Sopenharmony_ci activeKeys = self.GetCurrentKeySet() 4807db96d56Sopenharmony_ci extKeys = {} 4817db96d56Sopenharmony_ci if self.defaultCfg['extensions'].has_section(keysName): 4827db96d56Sopenharmony_ci eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) 4837db96d56Sopenharmony_ci for eventName in eventNames: 4847db96d56Sopenharmony_ci event = '<<' + eventName + '>>' 4857db96d56Sopenharmony_ci binding = activeKeys[event] 4867db96d56Sopenharmony_ci extKeys[event] = binding 4877db96d56Sopenharmony_ci return extKeys 4887db96d56Sopenharmony_ci 4897db96d56Sopenharmony_ci def __GetRawExtensionKeys(self,extensionName): 4907db96d56Sopenharmony_ci """Return dict {configurable extensionName event : keybinding list}. 4917db96d56Sopenharmony_ci 4927db96d56Sopenharmony_ci Events come from default config extension_cfgBindings section. 4937db96d56Sopenharmony_ci Keybindings list come from the splitting of GetOption, which 4947db96d56Sopenharmony_ci tries user config before default config. 4957db96d56Sopenharmony_ci """ 4967db96d56Sopenharmony_ci keysName = extensionName+'_cfgBindings' 4977db96d56Sopenharmony_ci extKeys = {} 4987db96d56Sopenharmony_ci if self.defaultCfg['extensions'].has_section(keysName): 4997db96d56Sopenharmony_ci eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) 5007db96d56Sopenharmony_ci for eventName in eventNames: 5017db96d56Sopenharmony_ci binding = self.GetOption( 5027db96d56Sopenharmony_ci 'extensions', keysName, eventName, default='').split() 5037db96d56Sopenharmony_ci event = '<<' + eventName + '>>' 5047db96d56Sopenharmony_ci extKeys[event] = binding 5057db96d56Sopenharmony_ci return extKeys 5067db96d56Sopenharmony_ci 5077db96d56Sopenharmony_ci def GetExtensionBindings(self, extensionName): 5087db96d56Sopenharmony_ci """Return dict {extensionName event : active or defined keybinding}. 5097db96d56Sopenharmony_ci 5107db96d56Sopenharmony_ci Augment self.GetExtensionKeys(extensionName) with mapping of non- 5117db96d56Sopenharmony_ci configurable events (from default config) to GetOption splits, 5127db96d56Sopenharmony_ci as in self.__GetRawExtensionKeys. 5137db96d56Sopenharmony_ci """ 5147db96d56Sopenharmony_ci bindsName = extensionName + '_bindings' 5157db96d56Sopenharmony_ci extBinds = self.GetExtensionKeys(extensionName) 5167db96d56Sopenharmony_ci #add the non-configurable bindings 5177db96d56Sopenharmony_ci if self.defaultCfg['extensions'].has_section(bindsName): 5187db96d56Sopenharmony_ci eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName) 5197db96d56Sopenharmony_ci for eventName in eventNames: 5207db96d56Sopenharmony_ci binding = self.GetOption( 5217db96d56Sopenharmony_ci 'extensions', bindsName, eventName, default='').split() 5227db96d56Sopenharmony_ci event = '<<' + eventName + '>>' 5237db96d56Sopenharmony_ci extBinds[event] = binding 5247db96d56Sopenharmony_ci 5257db96d56Sopenharmony_ci return extBinds 5267db96d56Sopenharmony_ci 5277db96d56Sopenharmony_ci def GetKeyBinding(self, keySetName, eventStr): 5287db96d56Sopenharmony_ci """Return the keybinding list for keySetName eventStr. 5297db96d56Sopenharmony_ci 5307db96d56Sopenharmony_ci keySetName - name of key binding set (config-keys section). 5317db96d56Sopenharmony_ci eventStr - virtual event, including brackets, as in '<<event>>'. 5327db96d56Sopenharmony_ci """ 5337db96d56Sopenharmony_ci eventName = eventStr[2:-2] #trim off the angle brackets 5347db96d56Sopenharmony_ci binding = self.GetOption('keys', keySetName, eventName, default='', 5357db96d56Sopenharmony_ci warn_on_default=False).split() 5367db96d56Sopenharmony_ci return binding 5377db96d56Sopenharmony_ci 5387db96d56Sopenharmony_ci def GetCurrentKeySet(self): 5397db96d56Sopenharmony_ci "Return CurrentKeys with 'darwin' modifications." 5407db96d56Sopenharmony_ci result = self.GetKeySet(self.CurrentKeys()) 5417db96d56Sopenharmony_ci 5427db96d56Sopenharmony_ci if sys.platform == "darwin": 5437db96d56Sopenharmony_ci # macOS (OS X) Tk variants do not support the "Alt" 5447db96d56Sopenharmony_ci # keyboard modifier. Replace it with "Option". 5457db96d56Sopenharmony_ci # TODO (Ned?): the "Option" modifier does not work properly 5467db96d56Sopenharmony_ci # for Cocoa Tk and XQuartz Tk so we should not use it 5477db96d56Sopenharmony_ci # in the default 'OSX' keyset. 5487db96d56Sopenharmony_ci for k, v in result.items(): 5497db96d56Sopenharmony_ci v2 = [ x.replace('<Alt-', '<Option-') for x in v ] 5507db96d56Sopenharmony_ci if v != v2: 5517db96d56Sopenharmony_ci result[k] = v2 5527db96d56Sopenharmony_ci 5537db96d56Sopenharmony_ci return result 5547db96d56Sopenharmony_ci 5557db96d56Sopenharmony_ci def GetKeySet(self, keySetName): 5567db96d56Sopenharmony_ci """Return event-key dict for keySetName core plus active extensions. 5577db96d56Sopenharmony_ci 5587db96d56Sopenharmony_ci If a binding defined in an extension is already in use, the 5597db96d56Sopenharmony_ci extension binding is disabled by being set to '' 5607db96d56Sopenharmony_ci """ 5617db96d56Sopenharmony_ci keySet = self.GetCoreKeys(keySetName) 5627db96d56Sopenharmony_ci activeExtns = self.GetExtensions(active_only=1) 5637db96d56Sopenharmony_ci for extn in activeExtns: 5647db96d56Sopenharmony_ci extKeys = self.__GetRawExtensionKeys(extn) 5657db96d56Sopenharmony_ci if extKeys: #the extension defines keybindings 5667db96d56Sopenharmony_ci for event in extKeys: 5677db96d56Sopenharmony_ci if extKeys[event] in keySet.values(): 5687db96d56Sopenharmony_ci #the binding is already in use 5697db96d56Sopenharmony_ci extKeys[event] = '' #disable this binding 5707db96d56Sopenharmony_ci keySet[event] = extKeys[event] #add binding 5717db96d56Sopenharmony_ci return keySet 5727db96d56Sopenharmony_ci 5737db96d56Sopenharmony_ci def IsCoreBinding(self, virtualEvent): 5747db96d56Sopenharmony_ci """Return True if the virtual event is one of the core idle key events. 5757db96d56Sopenharmony_ci 5767db96d56Sopenharmony_ci virtualEvent - string, name of the virtual event to test for, 5777db96d56Sopenharmony_ci without the enclosing '<< >>' 5787db96d56Sopenharmony_ci """ 5797db96d56Sopenharmony_ci return ('<<'+virtualEvent+'>>') in self.GetCoreKeys() 5807db96d56Sopenharmony_ci 5817db96d56Sopenharmony_ci# TODO make keyBindings a file or class attribute used for test above 5827db96d56Sopenharmony_ci# and copied in function below. 5837db96d56Sopenharmony_ci 5847db96d56Sopenharmony_ci former_extension_events = { # Those with user-configurable keys. 5857db96d56Sopenharmony_ci '<<force-open-completions>>', '<<expand-word>>', 5867db96d56Sopenharmony_ci '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>', 5877db96d56Sopenharmony_ci '<<run-module>>', '<<check-module>>', '<<zoom-height>>', 5887db96d56Sopenharmony_ci '<<run-custom>>', 5897db96d56Sopenharmony_ci } 5907db96d56Sopenharmony_ci 5917db96d56Sopenharmony_ci def GetCoreKeys(self, keySetName=None): 5927db96d56Sopenharmony_ci """Return dict of core virtual-key keybindings for keySetName. 5937db96d56Sopenharmony_ci 5947db96d56Sopenharmony_ci The default keySetName None corresponds to the keyBindings base 5957db96d56Sopenharmony_ci dict. If keySetName is not None, bindings from the config 5967db96d56Sopenharmony_ci file(s) are loaded _over_ these defaults, so if there is a 5977db96d56Sopenharmony_ci problem getting any core binding there will be an 'ultimate last 5987db96d56Sopenharmony_ci resort fallback' to the CUA-ish bindings defined here. 5997db96d56Sopenharmony_ci """ 6007db96d56Sopenharmony_ci keyBindings={ 6017db96d56Sopenharmony_ci '<<copy>>': ['<Control-c>', '<Control-C>'], 6027db96d56Sopenharmony_ci '<<cut>>': ['<Control-x>', '<Control-X>'], 6037db96d56Sopenharmony_ci '<<paste>>': ['<Control-v>', '<Control-V>'], 6047db96d56Sopenharmony_ci '<<beginning-of-line>>': ['<Control-a>', '<Home>'], 6057db96d56Sopenharmony_ci '<<center-insert>>': ['<Control-l>'], 6067db96d56Sopenharmony_ci '<<close-all-windows>>': ['<Control-q>'], 6077db96d56Sopenharmony_ci '<<close-window>>': ['<Alt-F4>'], 6087db96d56Sopenharmony_ci '<<do-nothing>>': ['<Control-x>'], 6097db96d56Sopenharmony_ci '<<end-of-file>>': ['<Control-d>'], 6107db96d56Sopenharmony_ci '<<python-docs>>': ['<F1>'], 6117db96d56Sopenharmony_ci '<<python-context-help>>': ['<Shift-F1>'], 6127db96d56Sopenharmony_ci '<<history-next>>': ['<Alt-n>'], 6137db96d56Sopenharmony_ci '<<history-previous>>': ['<Alt-p>'], 6147db96d56Sopenharmony_ci '<<interrupt-execution>>': ['<Control-c>'], 6157db96d56Sopenharmony_ci '<<view-restart>>': ['<F6>'], 6167db96d56Sopenharmony_ci '<<restart-shell>>': ['<Control-F6>'], 6177db96d56Sopenharmony_ci '<<open-class-browser>>': ['<Alt-c>'], 6187db96d56Sopenharmony_ci '<<open-module>>': ['<Alt-m>'], 6197db96d56Sopenharmony_ci '<<open-new-window>>': ['<Control-n>'], 6207db96d56Sopenharmony_ci '<<open-window-from-file>>': ['<Control-o>'], 6217db96d56Sopenharmony_ci '<<plain-newline-and-indent>>': ['<Control-j>'], 6227db96d56Sopenharmony_ci '<<print-window>>': ['<Control-p>'], 6237db96d56Sopenharmony_ci '<<redo>>': ['<Control-y>'], 6247db96d56Sopenharmony_ci '<<remove-selection>>': ['<Escape>'], 6257db96d56Sopenharmony_ci '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'], 6267db96d56Sopenharmony_ci '<<save-window-as-file>>': ['<Alt-s>'], 6277db96d56Sopenharmony_ci '<<save-window>>': ['<Control-s>'], 6287db96d56Sopenharmony_ci '<<select-all>>': ['<Alt-a>'], 6297db96d56Sopenharmony_ci '<<toggle-auto-coloring>>': ['<Control-slash>'], 6307db96d56Sopenharmony_ci '<<undo>>': ['<Control-z>'], 6317db96d56Sopenharmony_ci '<<find-again>>': ['<Control-g>', '<F3>'], 6327db96d56Sopenharmony_ci '<<find-in-files>>': ['<Alt-F3>'], 6337db96d56Sopenharmony_ci '<<find-selection>>': ['<Control-F3>'], 6347db96d56Sopenharmony_ci '<<find>>': ['<Control-f>'], 6357db96d56Sopenharmony_ci '<<replace>>': ['<Control-h>'], 6367db96d56Sopenharmony_ci '<<goto-line>>': ['<Alt-g>'], 6377db96d56Sopenharmony_ci '<<smart-backspace>>': ['<Key-BackSpace>'], 6387db96d56Sopenharmony_ci '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'], 6397db96d56Sopenharmony_ci '<<smart-indent>>': ['<Key-Tab>'], 6407db96d56Sopenharmony_ci '<<indent-region>>': ['<Control-Key-bracketright>'], 6417db96d56Sopenharmony_ci '<<dedent-region>>': ['<Control-Key-bracketleft>'], 6427db96d56Sopenharmony_ci '<<comment-region>>': ['<Alt-Key-3>'], 6437db96d56Sopenharmony_ci '<<uncomment-region>>': ['<Alt-Key-4>'], 6447db96d56Sopenharmony_ci '<<tabify-region>>': ['<Alt-Key-5>'], 6457db96d56Sopenharmony_ci '<<untabify-region>>': ['<Alt-Key-6>'], 6467db96d56Sopenharmony_ci '<<toggle-tabs>>': ['<Alt-Key-t>'], 6477db96d56Sopenharmony_ci '<<change-indentwidth>>': ['<Alt-Key-u>'], 6487db96d56Sopenharmony_ci '<<del-word-left>>': ['<Control-Key-BackSpace>'], 6497db96d56Sopenharmony_ci '<<del-word-right>>': ['<Control-Key-Delete>'], 6507db96d56Sopenharmony_ci '<<force-open-completions>>': ['<Control-Key-space>'], 6517db96d56Sopenharmony_ci '<<expand-word>>': ['<Alt-Key-slash>'], 6527db96d56Sopenharmony_ci '<<force-open-calltip>>': ['<Control-Key-backslash>'], 6537db96d56Sopenharmony_ci '<<flash-paren>>': ['<Control-Key-0>'], 6547db96d56Sopenharmony_ci '<<format-paragraph>>': ['<Alt-Key-q>'], 6557db96d56Sopenharmony_ci '<<run-module>>': ['<Key-F5>'], 6567db96d56Sopenharmony_ci '<<run-custom>>': ['<Shift-Key-F5>'], 6577db96d56Sopenharmony_ci '<<check-module>>': ['<Alt-Key-x>'], 6587db96d56Sopenharmony_ci '<<zoom-height>>': ['<Alt-Key-2>'], 6597db96d56Sopenharmony_ci } 6607db96d56Sopenharmony_ci 6617db96d56Sopenharmony_ci if keySetName: 6627db96d56Sopenharmony_ci if not (self.userCfg['keys'].has_section(keySetName) or 6637db96d56Sopenharmony_ci self.defaultCfg['keys'].has_section(keySetName)): 6647db96d56Sopenharmony_ci warning = ( 6657db96d56Sopenharmony_ci '\n Warning: config.py - IdleConf.GetCoreKeys -\n' 6667db96d56Sopenharmony_ci ' key set %r is not defined, using default bindings.' % 6677db96d56Sopenharmony_ci (keySetName,) 6687db96d56Sopenharmony_ci ) 6697db96d56Sopenharmony_ci _warn(warning, 'keys', keySetName) 6707db96d56Sopenharmony_ci else: 6717db96d56Sopenharmony_ci for event in keyBindings: 6727db96d56Sopenharmony_ci binding = self.GetKeyBinding(keySetName, event) 6737db96d56Sopenharmony_ci if binding: 6747db96d56Sopenharmony_ci keyBindings[event] = binding 6757db96d56Sopenharmony_ci # Otherwise return default in keyBindings. 6767db96d56Sopenharmony_ci elif event not in self.former_extension_events: 6777db96d56Sopenharmony_ci warning = ( 6787db96d56Sopenharmony_ci '\n Warning: config.py - IdleConf.GetCoreKeys -\n' 6797db96d56Sopenharmony_ci ' problem retrieving key binding for event %r\n' 6807db96d56Sopenharmony_ci ' from key set %r.\n' 6817db96d56Sopenharmony_ci ' returning default value: %r' % 6827db96d56Sopenharmony_ci (event, keySetName, keyBindings[event]) 6837db96d56Sopenharmony_ci ) 6847db96d56Sopenharmony_ci _warn(warning, 'keys', keySetName, event) 6857db96d56Sopenharmony_ci return keyBindings 6867db96d56Sopenharmony_ci 6877db96d56Sopenharmony_ci def GetExtraHelpSourceList(self, configSet): 6887db96d56Sopenharmony_ci """Return list of extra help sources from a given configSet. 6897db96d56Sopenharmony_ci 6907db96d56Sopenharmony_ci Valid configSets are 'user' or 'default'. Return a list of tuples of 6917db96d56Sopenharmony_ci the form (menu_item , path_to_help_file , option), or return the empty 6927db96d56Sopenharmony_ci list. 'option' is the sequence number of the help resource. 'option' 6937db96d56Sopenharmony_ci values determine the position of the menu items on the Help menu, 6947db96d56Sopenharmony_ci therefore the returned list must be sorted by 'option'. 6957db96d56Sopenharmony_ci 6967db96d56Sopenharmony_ci """ 6977db96d56Sopenharmony_ci helpSources = [] 6987db96d56Sopenharmony_ci if configSet == 'user': 6997db96d56Sopenharmony_ci cfgParser = self.userCfg['main'] 7007db96d56Sopenharmony_ci elif configSet == 'default': 7017db96d56Sopenharmony_ci cfgParser = self.defaultCfg['main'] 7027db96d56Sopenharmony_ci else: 7037db96d56Sopenharmony_ci raise InvalidConfigSet('Invalid configSet specified') 7047db96d56Sopenharmony_ci options=cfgParser.GetOptionList('HelpFiles') 7057db96d56Sopenharmony_ci for option in options: 7067db96d56Sopenharmony_ci value=cfgParser.Get('HelpFiles', option, default=';') 7077db96d56Sopenharmony_ci if value.find(';') == -1: #malformed config entry with no ';' 7087db96d56Sopenharmony_ci menuItem = '' #make these empty 7097db96d56Sopenharmony_ci helpPath = '' #so value won't be added to list 7107db96d56Sopenharmony_ci else: #config entry contains ';' as expected 7117db96d56Sopenharmony_ci value=value.split(';') 7127db96d56Sopenharmony_ci menuItem=value[0].strip() 7137db96d56Sopenharmony_ci helpPath=value[1].strip() 7147db96d56Sopenharmony_ci if menuItem and helpPath: #neither are empty strings 7157db96d56Sopenharmony_ci helpSources.append( (menuItem,helpPath,option) ) 7167db96d56Sopenharmony_ci helpSources.sort(key=lambda x: x[2]) 7177db96d56Sopenharmony_ci return helpSources 7187db96d56Sopenharmony_ci 7197db96d56Sopenharmony_ci def GetAllExtraHelpSourcesList(self): 7207db96d56Sopenharmony_ci """Return a list of the details of all additional help sources. 7217db96d56Sopenharmony_ci 7227db96d56Sopenharmony_ci Tuples in the list are those of GetExtraHelpSourceList. 7237db96d56Sopenharmony_ci """ 7247db96d56Sopenharmony_ci allHelpSources = (self.GetExtraHelpSourceList('default') + 7257db96d56Sopenharmony_ci self.GetExtraHelpSourceList('user') ) 7267db96d56Sopenharmony_ci return allHelpSources 7277db96d56Sopenharmony_ci 7287db96d56Sopenharmony_ci def GetFont(self, root, configType, section): 7297db96d56Sopenharmony_ci """Retrieve a font from configuration (font, font-size, font-bold) 7307db96d56Sopenharmony_ci Intercept the special value 'TkFixedFont' and substitute 7317db96d56Sopenharmony_ci the actual font, factoring in some tweaks if needed for 7327db96d56Sopenharmony_ci appearance sakes. 7337db96d56Sopenharmony_ci 7347db96d56Sopenharmony_ci The 'root' parameter can normally be any valid Tkinter widget. 7357db96d56Sopenharmony_ci 7367db96d56Sopenharmony_ci Return a tuple (family, size, weight) suitable for passing 7377db96d56Sopenharmony_ci to tkinter.Font 7387db96d56Sopenharmony_ci """ 7397db96d56Sopenharmony_ci family = self.GetOption(configType, section, 'font', default='courier') 7407db96d56Sopenharmony_ci size = self.GetOption(configType, section, 'font-size', type='int', 7417db96d56Sopenharmony_ci default='10') 7427db96d56Sopenharmony_ci bold = self.GetOption(configType, section, 'font-bold', default=0, 7437db96d56Sopenharmony_ci type='bool') 7447db96d56Sopenharmony_ci if (family == 'TkFixedFont'): 7457db96d56Sopenharmony_ci f = Font(name='TkFixedFont', exists=True, root=root) 7467db96d56Sopenharmony_ci actualFont = Font.actual(f) 7477db96d56Sopenharmony_ci family = actualFont['family'] 7487db96d56Sopenharmony_ci size = actualFont['size'] 7497db96d56Sopenharmony_ci if size <= 0: 7507db96d56Sopenharmony_ci size = 10 # if font in pixels, ignore actual size 7517db96d56Sopenharmony_ci bold = actualFont['weight'] == 'bold' 7527db96d56Sopenharmony_ci return (family, size, 'bold' if bold else 'normal') 7537db96d56Sopenharmony_ci 7547db96d56Sopenharmony_ci def LoadCfgFiles(self): 7557db96d56Sopenharmony_ci "Load all configuration files." 7567db96d56Sopenharmony_ci for key in self.defaultCfg: 7577db96d56Sopenharmony_ci self.defaultCfg[key].Load() 7587db96d56Sopenharmony_ci self.userCfg[key].Load() #same keys 7597db96d56Sopenharmony_ci 7607db96d56Sopenharmony_ci def SaveUserCfgFiles(self): 7617db96d56Sopenharmony_ci "Write all loaded user configuration files to disk." 7627db96d56Sopenharmony_ci for key in self.userCfg: 7637db96d56Sopenharmony_ci self.userCfg[key].Save() 7647db96d56Sopenharmony_ci 7657db96d56Sopenharmony_ci 7667db96d56Sopenharmony_ciidleConf = IdleConf() 7677db96d56Sopenharmony_ci 7687db96d56Sopenharmony_ci_warned = set() 7697db96d56Sopenharmony_cidef _warn(msg, *key): 7707db96d56Sopenharmony_ci key = (msg,) + key 7717db96d56Sopenharmony_ci if key not in _warned: 7727db96d56Sopenharmony_ci try: 7737db96d56Sopenharmony_ci print(msg, file=sys.stderr) 7747db96d56Sopenharmony_ci except OSError: 7757db96d56Sopenharmony_ci pass 7767db96d56Sopenharmony_ci _warned.add(key) 7777db96d56Sopenharmony_ci 7787db96d56Sopenharmony_ci 7797db96d56Sopenharmony_ciclass ConfigChanges(dict): 7807db96d56Sopenharmony_ci """Manage a user's proposed configuration option changes. 7817db96d56Sopenharmony_ci 7827db96d56Sopenharmony_ci Names used across multiple methods: 7837db96d56Sopenharmony_ci page -- one of the 4 top-level dicts representing a 7847db96d56Sopenharmony_ci .idlerc/config-x.cfg file. 7857db96d56Sopenharmony_ci config_type -- name of a page. 7867db96d56Sopenharmony_ci section -- a section within a page/file. 7877db96d56Sopenharmony_ci option -- name of an option within a section. 7887db96d56Sopenharmony_ci value -- value for the option. 7897db96d56Sopenharmony_ci 7907db96d56Sopenharmony_ci Methods 7917db96d56Sopenharmony_ci add_option: Add option and value to changes. 7927db96d56Sopenharmony_ci save_option: Save option and value to config parser. 7937db96d56Sopenharmony_ci save_all: Save all the changes to the config parser and file. 7947db96d56Sopenharmony_ci delete_section: If section exists, 7957db96d56Sopenharmony_ci delete from changes, userCfg, and file. 7967db96d56Sopenharmony_ci clear: Clear all changes by clearing each page. 7977db96d56Sopenharmony_ci """ 7987db96d56Sopenharmony_ci def __init__(self): 7997db96d56Sopenharmony_ci "Create a page for each configuration file" 8007db96d56Sopenharmony_ci self.pages = [] # List of unhashable dicts. 8017db96d56Sopenharmony_ci for config_type in idleConf.config_types: 8027db96d56Sopenharmony_ci self[config_type] = {} 8037db96d56Sopenharmony_ci self.pages.append(self[config_type]) 8047db96d56Sopenharmony_ci 8057db96d56Sopenharmony_ci def add_option(self, config_type, section, item, value): 8067db96d56Sopenharmony_ci "Add item/value pair for config_type and section." 8077db96d56Sopenharmony_ci page = self[config_type] 8087db96d56Sopenharmony_ci value = str(value) # Make sure we use a string. 8097db96d56Sopenharmony_ci if section not in page: 8107db96d56Sopenharmony_ci page[section] = {} 8117db96d56Sopenharmony_ci page[section][item] = value 8127db96d56Sopenharmony_ci 8137db96d56Sopenharmony_ci @staticmethod 8147db96d56Sopenharmony_ci def save_option(config_type, section, item, value): 8157db96d56Sopenharmony_ci """Return True if the configuration value was added or changed. 8167db96d56Sopenharmony_ci 8177db96d56Sopenharmony_ci Helper for save_all. 8187db96d56Sopenharmony_ci """ 8197db96d56Sopenharmony_ci if idleConf.defaultCfg[config_type].has_option(section, item): 8207db96d56Sopenharmony_ci if idleConf.defaultCfg[config_type].Get(section, item) == value: 8217db96d56Sopenharmony_ci # The setting equals a default setting, remove it from user cfg. 8227db96d56Sopenharmony_ci return idleConf.userCfg[config_type].RemoveOption(section, item) 8237db96d56Sopenharmony_ci # If we got here, set the option. 8247db96d56Sopenharmony_ci return idleConf.userCfg[config_type].SetOption(section, item, value) 8257db96d56Sopenharmony_ci 8267db96d56Sopenharmony_ci def save_all(self): 8277db96d56Sopenharmony_ci """Save configuration changes to the user config file. 8287db96d56Sopenharmony_ci 8297db96d56Sopenharmony_ci Clear self in preparation for additional changes. 8307db96d56Sopenharmony_ci Return changed for testing. 8317db96d56Sopenharmony_ci """ 8327db96d56Sopenharmony_ci idleConf.userCfg['main'].Save() 8337db96d56Sopenharmony_ci 8347db96d56Sopenharmony_ci changed = False 8357db96d56Sopenharmony_ci for config_type in self: 8367db96d56Sopenharmony_ci cfg_type_changed = False 8377db96d56Sopenharmony_ci page = self[config_type] 8387db96d56Sopenharmony_ci for section in page: 8397db96d56Sopenharmony_ci if section == 'HelpFiles': # Remove it for replacement. 8407db96d56Sopenharmony_ci idleConf.userCfg['main'].remove_section('HelpFiles') 8417db96d56Sopenharmony_ci cfg_type_changed = True 8427db96d56Sopenharmony_ci for item, value in page[section].items(): 8437db96d56Sopenharmony_ci if self.save_option(config_type, section, item, value): 8447db96d56Sopenharmony_ci cfg_type_changed = True 8457db96d56Sopenharmony_ci if cfg_type_changed: 8467db96d56Sopenharmony_ci idleConf.userCfg[config_type].Save() 8477db96d56Sopenharmony_ci changed = True 8487db96d56Sopenharmony_ci for config_type in ['keys', 'highlight']: 8497db96d56Sopenharmony_ci # Save these even if unchanged! 8507db96d56Sopenharmony_ci idleConf.userCfg[config_type].Save() 8517db96d56Sopenharmony_ci self.clear() 8527db96d56Sopenharmony_ci # ConfigDialog caller must add the following call 8537db96d56Sopenharmony_ci # self.save_all_changed_extensions() # Uses a different mechanism. 8547db96d56Sopenharmony_ci return changed 8557db96d56Sopenharmony_ci 8567db96d56Sopenharmony_ci def delete_section(self, config_type, section): 8577db96d56Sopenharmony_ci """Delete a section from self, userCfg, and file. 8587db96d56Sopenharmony_ci 8597db96d56Sopenharmony_ci Used to delete custom themes and keysets. 8607db96d56Sopenharmony_ci """ 8617db96d56Sopenharmony_ci if section in self[config_type]: 8627db96d56Sopenharmony_ci del self[config_type][section] 8637db96d56Sopenharmony_ci configpage = idleConf.userCfg[config_type] 8647db96d56Sopenharmony_ci configpage.remove_section(section) 8657db96d56Sopenharmony_ci configpage.Save() 8667db96d56Sopenharmony_ci 8677db96d56Sopenharmony_ci def clear(self): 8687db96d56Sopenharmony_ci """Clear all 4 pages. 8697db96d56Sopenharmony_ci 8707db96d56Sopenharmony_ci Called in save_all after saving to idleConf. 8717db96d56Sopenharmony_ci XXX Mark window *title* when there are changes; unmark here. 8727db96d56Sopenharmony_ci """ 8737db96d56Sopenharmony_ci for page in self.pages: 8747db96d56Sopenharmony_ci page.clear() 8757db96d56Sopenharmony_ci 8767db96d56Sopenharmony_ci 8777db96d56Sopenharmony_ci# TODO Revise test output, write expanded unittest 8787db96d56Sopenharmony_cidef _dump(): # htest # (not really, but ignore in coverage) 8797db96d56Sopenharmony_ci from zlib import crc32 8807db96d56Sopenharmony_ci line, crc = 0, 0 8817db96d56Sopenharmony_ci 8827db96d56Sopenharmony_ci def sprint(obj): 8837db96d56Sopenharmony_ci global line, crc 8847db96d56Sopenharmony_ci txt = str(obj) 8857db96d56Sopenharmony_ci line += 1 8867db96d56Sopenharmony_ci crc = crc32(txt.encode(encoding='utf-8'), crc) 8877db96d56Sopenharmony_ci print(txt) 8887db96d56Sopenharmony_ci #print('***', line, crc, '***') # Uncomment for diagnosis. 8897db96d56Sopenharmony_ci 8907db96d56Sopenharmony_ci def dumpCfg(cfg): 8917db96d56Sopenharmony_ci print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address. 8927db96d56Sopenharmony_ci for key in sorted(cfg.keys()): 8937db96d56Sopenharmony_ci sections = cfg[key].sections() 8947db96d56Sopenharmony_ci sprint(key) 8957db96d56Sopenharmony_ci sprint(sections) 8967db96d56Sopenharmony_ci for section in sections: 8977db96d56Sopenharmony_ci options = cfg[key].options(section) 8987db96d56Sopenharmony_ci sprint(section) 8997db96d56Sopenharmony_ci sprint(options) 9007db96d56Sopenharmony_ci for option in options: 9017db96d56Sopenharmony_ci sprint(option + ' = ' + cfg[key].Get(section, option)) 9027db96d56Sopenharmony_ci 9037db96d56Sopenharmony_ci dumpCfg(idleConf.defaultCfg) 9047db96d56Sopenharmony_ci dumpCfg(idleConf.userCfg) 9057db96d56Sopenharmony_ci print('\nlines = ', line, ', crc = ', crc, sep='') 9067db96d56Sopenharmony_ci 9077db96d56Sopenharmony_ciif __name__ == '__main__': 9087db96d56Sopenharmony_ci from unittest import main 9097db96d56Sopenharmony_ci main('idlelib.idle_test.test_config', verbosity=2, exit=False) 9107db96d56Sopenharmony_ci 9117db96d56Sopenharmony_ci # Run revised _dump() as htest? 912