blob: 9f63ea0d3990e6db84adc16ed1d191ab51787ea5 [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
Terry Jan Reedy01e35752016-06-10 18:19:21 -040031from tkinter import Toplevel, Frame, Text, Menu
32from tkinter.ttk import Menubutton, Scrollbar
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)
Miss Islington (bot)29825a32019-09-04 17:39:34 -070053 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):
Miss Islington (bot)29825a32019-09-04 17:39:34 -070068 "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':
Miss Islington (bot)29825a32019-09-04 17:39:34 -070080 self.show = True # Start main content.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040081 elif tag == 'div' and class_ == 'sphinxsidebar':
Miss Islington (bot)29825a32019-09-04 17:39:34 -070082 self.show = False # End main content.
Miss Islington (bot)952ea672019-09-03 14:18:45 -070083 elif tag == 'p' and self.prevtag and not self.prevtag[0]:
Miss Islington (bot)29825a32019-09-04 17:39:34 -070084 # Begin a new block for <p> tags after a closed tag.
85 # Avoid extra lines, e.g. after <pre> tags.
Miss Islington (bot)952ea672019-09-03 14:18:45 -070086 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':
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700107 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))
Miss Islington (bot)952ea672019-09-03 14:18:45 -0700128 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']:
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700133 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')))
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700139 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']:
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700148 self.indent(-1)
Miss Islington (bot)952ea672019-09-03 14:18:45 -0700149 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')
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700174 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)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400215 self['background'] = text['background']
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400216 self.toc = toc = self.toc_menu(text)
217 self.scroll = scroll = Scrollbar(self, command=text.yview)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400218 text['yscrollcommand'] = scroll.set
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400219
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400220 self.rowconfigure(0, weight=1)
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700221 self.columnconfigure(1, weight=1) # Only expand the text widget.
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400222 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')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400225
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400226 def toc_menu(self, text):
227 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400228 toc = Menubutton(self, text='TOC')
229 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400230 for lbl, dex in text.parser.toc:
231 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400232 toc['menu'] = drop
233 return toc
234
235
236class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400237 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400238 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
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400247def copy_strip():
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500248 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500249
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400250 Files with trailing whitespace cannot be pushed to the git cpython
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500251 repository. For 3.x (on Windows), help.html is generated, after
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400252 editing idle.rst on the master branch, with
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500253 sphinx-build -bhtml . build/html
254 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400255 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.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500257
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400258 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.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500262
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400263 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
Miss Islington (bot)a8cac572019-07-21 12:44:28 -0700267 master, repeat the procedure above to generate a maintenance
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400268 version.
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500269 """
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400270 src = join(abspath(dirname(dirname(dirname(__file__)))),
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400271 'Doc', 'build', 'html', 'library', 'idle.html')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400272 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:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400276 out.write(line.rstrip() + b'\n')
terryjreedy188aedf2017-06-13 21:32:16 -0400277 print(f'{src} copied to {dst}')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400278
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400279def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400280 "Create HelpWindow; called from Idle Help event handler."
281 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400282 if not isfile(filename):
Miss Islington (bot)29825a32019-09-04 17:39:34 -0700283 # Try copy_strip, present message.
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400284 return
Terry Jan Reedy39e9af62016-08-25 20:04:14 -0400285 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400286
287if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -0400288 from unittest import main
289 main('idlelib.idle_test.test_help', verbosity=2, exit=False)
290
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400291 from idlelib.idle_test.htest import run
292 run(show_idlehelp)