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