annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/help.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 """ help.py: Implement the Idle help menu.
jpayne@68 2 Contents are subject to revision at any time, without notice.
jpayne@68 3
jpayne@68 4
jpayne@68 5 Help => About IDLE: display About Idle dialog
jpayne@68 6
jpayne@68 7 <to be moved here from help_about.py>
jpayne@68 8
jpayne@68 9
jpayne@68 10 Help => IDLE Help: Display help.html with proper formatting.
jpayne@68 11 Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
jpayne@68 12 (help.copy_strip)=> Lib/idlelib/help.html
jpayne@68 13
jpayne@68 14 HelpParser - Parse help.html and render to tk Text.
jpayne@68 15
jpayne@68 16 HelpText - Display formatted help.html.
jpayne@68 17
jpayne@68 18 HelpFrame - Contain text, scrollbar, and table-of-contents.
jpayne@68 19 (This will be needed for display in a future tabbed window.)
jpayne@68 20
jpayne@68 21 HelpWindow - Display HelpFrame in a standalone window.
jpayne@68 22
jpayne@68 23 copy_strip - Copy idle.html to help.html, rstripping each line.
jpayne@68 24
jpayne@68 25 show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
jpayne@68 26 """
jpayne@68 27 from html.parser import HTMLParser
jpayne@68 28 from os.path import abspath, dirname, isfile, join
jpayne@68 29 from platform import python_version
jpayne@68 30
jpayne@68 31 from tkinter import Toplevel, Frame, Text, Menu
jpayne@68 32 from tkinter.ttk import Menubutton, Scrollbar
jpayne@68 33 from tkinter import font as tkfont
jpayne@68 34
jpayne@68 35 from idlelib.config import idleConf
jpayne@68 36
jpayne@68 37 ## About IDLE ##
jpayne@68 38
jpayne@68 39
jpayne@68 40 ## IDLE Help ##
jpayne@68 41
jpayne@68 42 class HelpParser(HTMLParser):
jpayne@68 43 """Render help.html into a text widget.
jpayne@68 44
jpayne@68 45 The overridden handle_xyz methods handle a subset of html tags.
jpayne@68 46 The supplied text should have the needed tag configurations.
jpayne@68 47 The behavior for unsupported tags, such as table, is undefined.
jpayne@68 48 If the tags generated by Sphinx change, this class, especially
jpayne@68 49 the handle_starttag and handle_endtags methods, might have to also.
jpayne@68 50 """
jpayne@68 51 def __init__(self, text):
jpayne@68 52 HTMLParser.__init__(self, convert_charrefs=True)
jpayne@68 53 self.text = text # Text widget we're rendering into.
jpayne@68 54 self.tags = '' # Current block level text tags to apply.
jpayne@68 55 self.chartags = '' # Current character level text tags.
jpayne@68 56 self.show = False # Exclude html page navigation.
jpayne@68 57 self.hdrlink = False # Exclude html header links.
jpayne@68 58 self.level = 0 # Track indentation level.
jpayne@68 59 self.pre = False # Displaying preformatted text?
jpayne@68 60 self.hprefix = '' # Heading prefix (like '25.5'?) to remove.
jpayne@68 61 self.nested_dl = False # In a nested <dl>?
jpayne@68 62 self.simplelist = False # In a simple list (no double spacing)?
jpayne@68 63 self.toc = [] # Pair headers with text indexes for toc.
jpayne@68 64 self.header = '' # Text within header tags for toc.
jpayne@68 65 self.prevtag = None # Previous tag info (opener?, tag).
jpayne@68 66
jpayne@68 67 def indent(self, amt=1):
jpayne@68 68 "Change indent (+1, 0, -1) and tags."
jpayne@68 69 self.level += amt
jpayne@68 70 self.tags = '' if self.level == 0 else 'l'+str(self.level)
jpayne@68 71
jpayne@68 72 def handle_starttag(self, tag, attrs):
jpayne@68 73 "Handle starttags in help.html."
jpayne@68 74 class_ = ''
jpayne@68 75 for a, v in attrs:
jpayne@68 76 if a == 'class':
jpayne@68 77 class_ = v
jpayne@68 78 s = ''
jpayne@68 79 if tag == 'div' and class_ == 'section':
jpayne@68 80 self.show = True # Start main content.
jpayne@68 81 elif tag == 'div' and class_ == 'sphinxsidebar':
jpayne@68 82 self.show = False # End main content.
jpayne@68 83 elif tag == 'p' and self.prevtag and not self.prevtag[0]:
jpayne@68 84 # Begin a new block for <p> tags after a closed tag.
jpayne@68 85 # Avoid extra lines, e.g. after <pre> tags.
jpayne@68 86 lastline = self.text.get('end-1c linestart', 'end-1c')
jpayne@68 87 s = '\n\n' if lastline and not lastline.isspace() else '\n'
jpayne@68 88 elif tag == 'span' and class_ == 'pre':
jpayne@68 89 self.chartags = 'pre'
jpayne@68 90 elif tag == 'span' and class_ == 'versionmodified':
jpayne@68 91 self.chartags = 'em'
jpayne@68 92 elif tag == 'em':
jpayne@68 93 self.chartags = 'em'
jpayne@68 94 elif tag in ['ul', 'ol']:
jpayne@68 95 if class_.find('simple') != -1:
jpayne@68 96 s = '\n'
jpayne@68 97 self.simplelist = True
jpayne@68 98 else:
jpayne@68 99 self.simplelist = False
jpayne@68 100 self.indent()
jpayne@68 101 elif tag == 'dl':
jpayne@68 102 if self.level > 0:
jpayne@68 103 self.nested_dl = True
jpayne@68 104 elif tag == 'li':
jpayne@68 105 s = '\n* ' if self.simplelist else '\n\n* '
jpayne@68 106 elif tag == 'dt':
jpayne@68 107 s = '\n\n' if not self.nested_dl else '\n' # Avoid extra line.
jpayne@68 108 self.nested_dl = False
jpayne@68 109 elif tag == 'dd':
jpayne@68 110 self.indent()
jpayne@68 111 s = '\n'
jpayne@68 112 elif tag == 'pre':
jpayne@68 113 self.pre = True
jpayne@68 114 if self.show:
jpayne@68 115 self.text.insert('end', '\n\n')
jpayne@68 116 self.tags = 'preblock'
jpayne@68 117 elif tag == 'a' and class_ == 'headerlink':
jpayne@68 118 self.hdrlink = True
jpayne@68 119 elif tag == 'h1':
jpayne@68 120 self.tags = tag
jpayne@68 121 elif tag in ['h2', 'h3']:
jpayne@68 122 if self.show:
jpayne@68 123 self.header = ''
jpayne@68 124 self.text.insert('end', '\n\n')
jpayne@68 125 self.tags = tag
jpayne@68 126 if self.show:
jpayne@68 127 self.text.insert('end', s, (self.tags, self.chartags))
jpayne@68 128 self.prevtag = (True, tag)
jpayne@68 129
jpayne@68 130 def handle_endtag(self, tag):
jpayne@68 131 "Handle endtags in help.html."
jpayne@68 132 if tag in ['h1', 'h2', 'h3']:
jpayne@68 133 assert self.level == 0
jpayne@68 134 if self.show:
jpayne@68 135 indent = (' ' if tag == 'h3' else
jpayne@68 136 ' ' if tag == 'h2' else
jpayne@68 137 '')
jpayne@68 138 self.toc.append((indent+self.header, self.text.index('insert')))
jpayne@68 139 self.tags = ''
jpayne@68 140 elif tag in ['span', 'em']:
jpayne@68 141 self.chartags = ''
jpayne@68 142 elif tag == 'a':
jpayne@68 143 self.hdrlink = False
jpayne@68 144 elif tag == 'pre':
jpayne@68 145 self.pre = False
jpayne@68 146 self.tags = ''
jpayne@68 147 elif tag in ['ul', 'dd', 'ol']:
jpayne@68 148 self.indent(-1)
jpayne@68 149 self.prevtag = (False, tag)
jpayne@68 150
jpayne@68 151 def handle_data(self, data):
jpayne@68 152 "Handle date segments in help.html."
jpayne@68 153 if self.show and not self.hdrlink:
jpayne@68 154 d = data if self.pre else data.replace('\n', ' ')
jpayne@68 155 if self.tags == 'h1':
jpayne@68 156 try:
jpayne@68 157 self.hprefix = d[0:d.index(' ')]
jpayne@68 158 except ValueError:
jpayne@68 159 self.hprefix = ''
jpayne@68 160 if self.tags in ['h1', 'h2', 'h3']:
jpayne@68 161 if (self.hprefix != '' and
jpayne@68 162 d[0:len(self.hprefix)] == self.hprefix):
jpayne@68 163 d = d[len(self.hprefix):]
jpayne@68 164 self.header += d.strip()
jpayne@68 165 self.text.insert('end', d, (self.tags, self.chartags))
jpayne@68 166
jpayne@68 167
jpayne@68 168 class HelpText(Text):
jpayne@68 169 "Display help.html."
jpayne@68 170 def __init__(self, parent, filename):
jpayne@68 171 "Configure tags and feed file to parser."
jpayne@68 172 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
jpayne@68 173 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
jpayne@68 174 uhigh = 3 * uhigh // 4 # Lines average 4/3 of editor line height.
jpayne@68 175 Text.__init__(self, parent, wrap='word', highlightthickness=0,
jpayne@68 176 padx=5, borderwidth=0, width=uwide, height=uhigh)
jpayne@68 177
jpayne@68 178 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
jpayne@68 179 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
jpayne@68 180 self['font'] = (normalfont, 12)
jpayne@68 181 self.tag_configure('em', font=(normalfont, 12, 'italic'))
jpayne@68 182 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
jpayne@68 183 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
jpayne@68 184 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
jpayne@68 185 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
jpayne@68 186 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
jpayne@68 187 borderwidth=1, relief='solid', background='#eeffcc')
jpayne@68 188 self.tag_configure('l1', lmargin1=25, lmargin2=25)
jpayne@68 189 self.tag_configure('l2', lmargin1=50, lmargin2=50)
jpayne@68 190 self.tag_configure('l3', lmargin1=75, lmargin2=75)
jpayne@68 191 self.tag_configure('l4', lmargin1=100, lmargin2=100)
jpayne@68 192
jpayne@68 193 self.parser = HelpParser(self)
jpayne@68 194 with open(filename, encoding='utf-8') as f:
jpayne@68 195 contents = f.read()
jpayne@68 196 self.parser.feed(contents)
jpayne@68 197 self['state'] = 'disabled'
jpayne@68 198
jpayne@68 199 def findfont(self, names):
jpayne@68 200 "Return name of first font family derived from names."
jpayne@68 201 for name in names:
jpayne@68 202 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
jpayne@68 203 font = tkfont.Font(name=name, exists=True, root=self)
jpayne@68 204 return font.actual()['family']
jpayne@68 205 elif name.lower() in (x.lower()
jpayne@68 206 for x in tkfont.families(root=self)):
jpayne@68 207 return name
jpayne@68 208
jpayne@68 209
jpayne@68 210 class HelpFrame(Frame):
jpayne@68 211 "Display html text, scrollbar, and toc."
jpayne@68 212 def __init__(self, parent, filename):
jpayne@68 213 Frame.__init__(self, parent)
jpayne@68 214 self.text = text = HelpText(self, filename)
jpayne@68 215 self['background'] = text['background']
jpayne@68 216 self.toc = toc = self.toc_menu(text)
jpayne@68 217 self.scroll = scroll = Scrollbar(self, command=text.yview)
jpayne@68 218 text['yscrollcommand'] = scroll.set
jpayne@68 219
jpayne@68 220 self.rowconfigure(0, weight=1)
jpayne@68 221 self.columnconfigure(1, weight=1) # Only expand the text widget.
jpayne@68 222 toc.grid(row=0, column=0, sticky='nw')
jpayne@68 223 text.grid(row=0, column=1, sticky='nsew')
jpayne@68 224 scroll.grid(row=0, column=2, sticky='ns')
jpayne@68 225
jpayne@68 226 def toc_menu(self, text):
jpayne@68 227 "Create table of contents as drop-down menu."
jpayne@68 228 toc = Menubutton(self, text='TOC')
jpayne@68 229 drop = Menu(toc, tearoff=False)
jpayne@68 230 for lbl, dex in text.parser.toc:
jpayne@68 231 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
jpayne@68 232 toc['menu'] = drop
jpayne@68 233 return toc
jpayne@68 234
jpayne@68 235
jpayne@68 236 class HelpWindow(Toplevel):
jpayne@68 237 "Display frame with rendered html."
jpayne@68 238 def __init__(self, parent, filename, title):
jpayne@68 239 Toplevel.__init__(self, parent)
jpayne@68 240 self.wm_title(title)
jpayne@68 241 self.protocol("WM_DELETE_WINDOW", self.destroy)
jpayne@68 242 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
jpayne@68 243 self.grid_columnconfigure(0, weight=1)
jpayne@68 244 self.grid_rowconfigure(0, weight=1)
jpayne@68 245
jpayne@68 246
jpayne@68 247 def copy_strip():
jpayne@68 248 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
jpayne@68 249
jpayne@68 250 Files with trailing whitespace cannot be pushed to the git cpython
jpayne@68 251 repository. For 3.x (on Windows), help.html is generated, after
jpayne@68 252 editing idle.rst on the master branch, with
jpayne@68 253 sphinx-build -bhtml . build/html
jpayne@68 254 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
jpayne@68 255 Check build/html/library/idle.html, the help.html diff, and the text
jpayne@68 256 displayed by Help => IDLE Help. Add a blurb and create a PR.
jpayne@68 257
jpayne@68 258 It can be worthwhile to occasionally generate help.html without
jpayne@68 259 touching idle.rst. Changes to the master version and to the doc
jpayne@68 260 build system may result in changes that should not changed
jpayne@68 261 the displayed text, but might break HelpParser.
jpayne@68 262
jpayne@68 263 As long as master and maintenance versions of idle.rst remain the
jpayne@68 264 same, help.html can be backported. The internal Python version
jpayne@68 265 number is not displayed. If maintenance idle.rst diverges from
jpayne@68 266 the master version, then instead of backporting help.html from
jpayne@68 267 master, repeat the procedure above to generate a maintenance
jpayne@68 268 version.
jpayne@68 269 """
jpayne@68 270 src = join(abspath(dirname(dirname(dirname(__file__)))),
jpayne@68 271 'Doc', 'build', 'html', 'library', 'idle.html')
jpayne@68 272 dst = join(abspath(dirname(__file__)), 'help.html')
jpayne@68 273 with open(src, 'rb') as inn,\
jpayne@68 274 open(dst, 'wb') as out:
jpayne@68 275 for line in inn:
jpayne@68 276 out.write(line.rstrip() + b'\n')
jpayne@68 277 print(f'{src} copied to {dst}')
jpayne@68 278
jpayne@68 279 def show_idlehelp(parent):
jpayne@68 280 "Create HelpWindow; called from Idle Help event handler."
jpayne@68 281 filename = join(abspath(dirname(__file__)), 'help.html')
jpayne@68 282 if not isfile(filename):
jpayne@68 283 # Try copy_strip, present message.
jpayne@68 284 return
jpayne@68 285 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
jpayne@68 286
jpayne@68 287 if __name__ == '__main__':
jpayne@68 288 from unittest import main
jpayne@68 289 main('idlelib.idle_test.test_help', verbosity=2, exit=False)
jpayne@68 290
jpayne@68 291 from idlelib.idle_test.htest import run
jpayne@68 292 run(show_idlehelp)