Mercurial > repos > rliterman > csp2
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) |