blob: f420d40fb9ea404270e96c37b831c279a0b428d6 [file] [log] [blame]
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -04001""" help.py: Implement the Idle help menu.
2Contents are subject to revision at any time, without notice.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04003
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04004
penguindustin96466302019-05-06 14:57:17 -04005Help => About IDLE: display About Idle dialog
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04006
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -04007<to be moved here from help_about.py>
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04008
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -04009
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040010Help => IDLE Help: Display help.html with proper formatting.
11Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
12(help.copy_strip)=> Lib/idlelib/help.html
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040013
Martin Panter96a4f072016-02-10 01:17:51 +000014HelpParser - Parse help.html and render to tk Text.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040015
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040016HelpText - Display formatted help.html.
17
18HelpFrame - Contain text, scrollbar, and table-of-contents.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040019(This will be needed for display in a future tabbed window.)
20
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040021HelpWindow - Display HelpFrame in a standalone window.
22
23copy_strip - Copy idle.html to help.html, rstripping each line.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040024
25show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
26"""
27from html.parser import HTMLParser
Serhiy Storchakaccd047e2016-04-25 00:12:32 +030028from os.path import abspath, dirname, isfile, join
Terry Jan Reedy39e9af62016-08-25 20:04:14 -040029from platform import python_version
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040030
Cheryl Sabellafacb5222020-10-22 15:14:35 -040031from tkinter import Toplevel, Text, Menu
32from tkinter.ttk import Frame, Menubutton, Scrollbar, Style
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040033from tkinter import font as tkfont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040034
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040035from idlelib.config import idleConf
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040036
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040037## About IDLE ##
38
39
40## IDLE Help ##
41
42class HelpParser(HTMLParser):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040043 """Render help.html into a text widget.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040044
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.
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -050048 If the tags generated by Sphinx change, this class, especially
49 the handle_starttag and handle_endtags methods, might have to also.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040050 """
51 def __init__(self, text):
52 HTMLParser.__init__(self, convert_charrefs=True)
Terry Jan Reedy6cd96662019-09-04 20:20:08 -040053 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).
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040066
67 def indent(self, amt=1):
Terry Jan Reedy6cd96662019-09-04 20:20:08 -040068 "Change indent (+1, 0, -1) and tags."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040069 self.level += amt
70 self.tags = '' if self.level == 0 else 'l'+str(self.level)
71
72 def handle_starttag(self, tag, attrs):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040073 "Handle starttags in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040074 class_ = ''
75 for a, v in attrs:
76 if a == 'class':
77 class_ = v
78 s = ''
79 if tag == 'div' and class_ == 'section':
Terry Jan Reedy6cd96662019-09-04 20:20:08 -040080 self.show = True # Start main content.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040081 elif tag == 'div' and class_ == 'sphinxsidebar':
Terry Jan Reedy6cd96662019-09-04 20:20:08 -040082 self.show = False # End main content.
Tal Einat580bdb02019-09-03 23:52:58 +030083 elif tag == 'p' and self.prevtag and not self.prevtag[0]:
Terry Jan Reedy6cd96662019-09-04 20:20:08 -040084 # Begin a new block for <p> tags after a closed tag.
85 # Avoid extra lines, e.g. after <pre> tags.
Tal Einat580bdb02019-09-03 23:52:58 +030086 lastline = self.text.get('end-1c linestart', 'end-1c')
87 s = '\n\n' if lastline and not lastline.isspace() else '\n'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040088 elif tag == 'span' and class_ == 'pre':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040089 self.chartags = 'pre'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040090 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040091 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040092 elif tag == 'em':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040093 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040094 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':
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400107 s = '\n\n' if not self.nested_dl else '\n' # Avoid extra line.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400108 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':
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400120 self.tags = tag
121 elif tag in ['h2', 'h3']:
122 if self.show:
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400123 self.header = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400124 self.text.insert('end', '\n\n')
125 self.tags = tag
126 if self.show:
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400127 self.text.insert('end', s, (self.tags, self.chartags))
Tal Einat580bdb02019-09-03 23:52:58 +0300128 self.prevtag = (True, tag)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400129
130 def handle_endtag(self, tag):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400131 "Handle endtags in help.html."
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400132 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400133 assert self.level == 0
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400134 if self.show:
Terry Jan Reedydb40cb52018-10-28 01:21:36 -0400135 indent = (' ' if tag == 'h3' else
136 ' ' if tag == 'h2' else
137 '')
138 self.toc.append((indent+self.header, self.text.index('insert')))
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400139 self.tags = ''
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400140 elif tag in ['span', 'em']:
141 self.chartags = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400142 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']:
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400148 self.indent(-1)
Tal Einat580bdb02019-09-03 23:52:58 +0300149 self.prevtag = (False, tag)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400150
151 def handle_data(self, data):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400152 "Handle date segments in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400153 if self.show and not self.hdrlink:
154 d = data if self.pre else data.replace('\n', ' ')
155 if self.tags == 'h1':
Terry Jan Reedydb40cb52018-10-28 01:21:36 -0400156 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()
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400165 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400166
167
168class HelpText(Text):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400169 "Display help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400170 def __init__(self, parent, filename):
171 "Configure tags and feed file to parser."
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400172 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
173 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400174 uhigh = 3 * uhigh // 4 # Lines average 4/3 of editor line height.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400175 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400176 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400177
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'))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400185 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400186 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
210class HelpFrame(Frame):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400211 "Display html text, scrollbar, and toc."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400212 def __init__(self, parent, filename):
213 Frame.__init__(self, parent)
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400214 self.text = text = HelpText(self, filename)
Cheryl Sabellafacb5222020-10-22 15:14:35 -0400215 self.style = Style(parent)
216 self['style'] = 'helpframe.TFrame'
217 self.style.configure('helpframe.TFrame', background=text['background'])
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400218 self.toc = toc = self.toc_menu(text)
219 self.scroll = scroll = Scrollbar(self, command=text.yview)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400220 text['yscrollcommand'] = scroll.set
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400221
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400222 self.rowconfigure(0, weight=1)
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400223 self.columnconfigure(1, weight=1) # Only expand the text widget.
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400224 toc.grid(row=0, column=0, sticky='nw')
225 text.grid(row=0, column=1, sticky='nsew')
226 scroll.grid(row=0, column=2, sticky='ns')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400227
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400228 def toc_menu(self, text):
229 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400230 toc = Menubutton(self, text='TOC')
231 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400232 for lbl, dex in text.parser.toc:
233 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400234 toc['menu'] = drop
235 return toc
236
237
238class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400239 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400240 def __init__(self, parent, filename, title):
241 Toplevel.__init__(self, parent)
242 self.wm_title(title)
243 self.protocol("WM_DELETE_WINDOW", self.destroy)
244 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
245 self.grid_columnconfigure(0, weight=1)
246 self.grid_rowconfigure(0, weight=1)
247
248
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400249def copy_strip():
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500250 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500251
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400252 Files with trailing whitespace cannot be pushed to the git cpython
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500253 repository. For 3.x (on Windows), help.html is generated, after
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400254 editing idle.rst on the master branch, with
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500255 sphinx-build -bhtml . build/html
256 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400257 Check build/html/library/idle.html, the help.html diff, and the text
258 displayed by Help => IDLE Help. Add a blurb and create a PR.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500259
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400260 It can be worthwhile to occasionally generate help.html without
261 touching idle.rst. Changes to the master version and to the doc
262 build system may result in changes that should not changed
263 the displayed text, but might break HelpParser.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500264
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400265 As long as master and maintenance versions of idle.rst remain the
266 same, help.html can be backported. The internal Python version
267 number is not displayed. If maintenance idle.rst diverges from
268 the master version, then instead of backporting help.html from
Terry Jan Reedy8e3a7382019-07-21 15:24:45 -0400269 master, repeat the procedure above to generate a maintenance
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400270 version.
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500271 """
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400272 src = join(abspath(dirname(dirname(dirname(__file__)))),
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400273 'Doc', 'build', 'html', 'library', 'idle.html')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400274 dst = join(abspath(dirname(__file__)), 'help.html')
275 with open(src, 'rb') as inn,\
276 open(dst, 'wb') as out:
277 for line in inn:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400278 out.write(line.rstrip() + b'\n')
terryjreedy188aedf2017-06-13 21:32:16 -0400279 print(f'{src} copied to {dst}')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400280
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400281def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400282 "Create HelpWindow; called from Idle Help event handler."
283 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400284 if not isfile(filename):
Terry Jan Reedy6cd96662019-09-04 20:20:08 -0400285 # Try copy_strip, present message.
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400286 return
Terry Jan Reedy39e9af62016-08-25 20:04:14 -0400287 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400288
289if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -0400290 from unittest import main
291 main('idlelib.idle_test.test_help', verbosity=2, exit=False)
292
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400293 from idlelib.idle_test.htest import run
294 run(show_idlehelp)