blob: ba2c6ec554812b56cd4766e57b23968ddb5965f2 [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)
53 self.text = text # text widget we're rendering into
Terry Jan Reedy974a2712015-09-24 17:32:01 -040054 self.tags = '' # current block level text tags to apply
55 self.chartags = '' # current character level text tags
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040056 self.show = False # used so we exclude page navigation
57 self.hdrlink = False # used so we don't show header links
58 self.level = 0 # indentation level
59 self.pre = False # displaying preformatted text
Terry Jan Reedy28670d12015-09-27 04:40:08 -040060 self.hprefix = '' # prefix such as '25.5' to strip from headings
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040061 self.nested_dl = False # if we're in a nested <dl>
62 self.simplelist = False # simple list (no double spacing)
Terry Jan Reedy28670d12015-09-27 04:40:08 -040063 self.toc = [] # pair headers with text indexes for toc
64 self.header = '' # text within header tags for toc
Tal Einat580bdb02019-09-03 23:52:58 +030065 self.prevtag = None # info about previous tag (was opener, tag)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040066
67 def indent(self, amt=1):
68 self.level += amt
69 self.tags = '' if self.level == 0 else 'l'+str(self.level)
70
71 def handle_starttag(self, tag, attrs):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040072 "Handle starttags in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040073 class_ = ''
74 for a, v in attrs:
75 if a == 'class':
76 class_ = v
77 s = ''
78 if tag == 'div' and class_ == 'section':
79 self.show = True # start of main content
80 elif tag == 'div' and class_ == 'sphinxsidebar':
81 self.show = False # end of main content
Tal Einat580bdb02019-09-03 23:52:58 +030082 elif tag == 'p' and self.prevtag and not self.prevtag[0]:
83 # begin a new block for <p> tags after a closed tag
84 # avoid extra lines, e.g. after <pre> tags
85 lastline = self.text.get('end-1c linestart', 'end-1c')
86 s = '\n\n' if lastline and not lastline.isspace() else '\n'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040087 elif tag == 'span' and class_ == 'pre':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040088 self.chartags = 'pre'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040089 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040090 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040091 elif tag == 'em':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040092 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040093 elif tag in ['ul', 'ol']:
94 if class_.find('simple') != -1:
95 s = '\n'
96 self.simplelist = True
97 else:
98 self.simplelist = False
99 self.indent()
100 elif tag == 'dl':
101 if self.level > 0:
102 self.nested_dl = True
103 elif tag == 'li':
104 s = '\n* ' if self.simplelist else '\n\n* '
105 elif tag == 'dt':
106 s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
107 self.nested_dl = False
108 elif tag == 'dd':
109 self.indent()
110 s = '\n'
111 elif tag == 'pre':
112 self.pre = True
113 if self.show:
114 self.text.insert('end', '\n\n')
115 self.tags = 'preblock'
116 elif tag == 'a' and class_ == 'headerlink':
117 self.hdrlink = True
118 elif tag == 'h1':
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400119 self.tags = tag
120 elif tag in ['h2', 'h3']:
121 if self.show:
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400122 self.header = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400123 self.text.insert('end', '\n\n')
124 self.tags = tag
125 if self.show:
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400126 self.text.insert('end', s, (self.tags, self.chartags))
Tal Einat580bdb02019-09-03 23:52:58 +0300127 self.prevtag = (True, tag)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400128
129 def handle_endtag(self, tag):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400130 "Handle endtags in help.html."
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400131 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400132 self.indent(0) # clear tag, reset indent
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400133 if self.show:
Terry Jan Reedydb40cb52018-10-28 01:21:36 -0400134 indent = (' ' if tag == 'h3' else
135 ' ' if tag == 'h2' else
136 '')
137 self.toc.append((indent+self.header, self.text.index('insert')))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400138 elif tag in ['span', 'em']:
139 self.chartags = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400140 elif tag == 'a':
141 self.hdrlink = False
142 elif tag == 'pre':
143 self.pre = False
144 self.tags = ''
145 elif tag in ['ul', 'dd', 'ol']:
146 self.indent(amt=-1)
Tal Einat580bdb02019-09-03 23:52:58 +0300147 self.prevtag = (False, tag)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400148
149 def handle_data(self, data):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400150 "Handle date segments in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400151 if self.show and not self.hdrlink:
152 d = data if self.pre else data.replace('\n', ' ')
153 if self.tags == 'h1':
Terry Jan Reedydb40cb52018-10-28 01:21:36 -0400154 try:
155 self.hprefix = d[0:d.index(' ')]
156 except ValueError:
157 self.hprefix = ''
158 if self.tags in ['h1', 'h2', 'h3']:
159 if (self.hprefix != '' and
160 d[0:len(self.hprefix)] == self.hprefix):
161 d = d[len(self.hprefix):]
162 self.header += d.strip()
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400163 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400164
165
166class HelpText(Text):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400167 "Display help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400168 def __init__(self, parent, filename):
169 "Configure tags and feed file to parser."
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400170 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
171 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
172 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400173 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400174 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400175
176 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
177 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
178 self['font'] = (normalfont, 12)
179 self.tag_configure('em', font=(normalfont, 12, 'italic'))
180 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
181 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
182 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400183 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400184 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
185 borderwidth=1, relief='solid', background='#eeffcc')
186 self.tag_configure('l1', lmargin1=25, lmargin2=25)
187 self.tag_configure('l2', lmargin1=50, lmargin2=50)
188 self.tag_configure('l3', lmargin1=75, lmargin2=75)
189 self.tag_configure('l4', lmargin1=100, lmargin2=100)
190
191 self.parser = HelpParser(self)
192 with open(filename, encoding='utf-8') as f:
193 contents = f.read()
194 self.parser.feed(contents)
195 self['state'] = 'disabled'
196
197 def findfont(self, names):
198 "Return name of first font family derived from names."
199 for name in names:
200 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
201 font = tkfont.Font(name=name, exists=True, root=self)
202 return font.actual()['family']
203 elif name.lower() in (x.lower()
204 for x in tkfont.families(root=self)):
205 return name
206
207
208class HelpFrame(Frame):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400209 "Display html text, scrollbar, and toc."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400210 def __init__(self, parent, filename):
211 Frame.__init__(self, parent)
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400212 # keep references to widgets for test access.
213 self.text = text = HelpText(self, filename)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400214 self['background'] = text['background']
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400215 self.toc = toc = self.toc_menu(text)
216 self.scroll = scroll = Scrollbar(self, command=text.yview)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400217 text['yscrollcommand'] = scroll.set
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400218
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400219 self.rowconfigure(0, weight=1)
220 self.columnconfigure(1, weight=1) # text
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400221 toc.grid(row=0, column=0, sticky='nw')
222 text.grid(row=0, column=1, sticky='nsew')
223 scroll.grid(row=0, column=2, sticky='ns')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400224
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400225 def toc_menu(self, text):
226 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400227 toc = Menubutton(self, text='TOC')
228 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400229 for lbl, dex in text.parser.toc:
230 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400231 toc['menu'] = drop
232 return toc
233
234
235class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400236 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400237 def __init__(self, parent, filename, title):
238 Toplevel.__init__(self, parent)
239 self.wm_title(title)
240 self.protocol("WM_DELETE_WINDOW", self.destroy)
241 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
242 self.grid_columnconfigure(0, weight=1)
243 self.grid_rowconfigure(0, weight=1)
244
245
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400246def copy_strip():
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500247 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500248
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400249 Files with trailing whitespace cannot be pushed to the git cpython
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500250 repository. For 3.x (on Windows), help.html is generated, after
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400251 editing idle.rst on the master branch, with
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500252 sphinx-build -bhtml . build/html
253 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400254 Check build/html/library/idle.html, the help.html diff, and the text
255 displayed by Help => IDLE Help. Add a blurb and create a PR.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500256
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400257 It can be worthwhile to occasionally generate help.html without
258 touching idle.rst. Changes to the master version and to the doc
259 build system may result in changes that should not changed
260 the displayed text, but might break HelpParser.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500261
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400262 As long as master and maintenance versions of idle.rst remain the
263 same, help.html can be backported. The internal Python version
264 number is not displayed. If maintenance idle.rst diverges from
265 the master version, then instead of backporting help.html from
Terry Jan Reedy8e3a7382019-07-21 15:24:45 -0400266 master, repeat the procedure above to generate a maintenance
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400267 version.
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500268 """
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400269 src = join(abspath(dirname(dirname(dirname(__file__)))),
Terry Jan Reedy2b555fc2018-10-28 01:29:00 -0400270 'Doc', 'build', 'html', 'library', 'idle.html')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400271 dst = join(abspath(dirname(__file__)), 'help.html')
272 with open(src, 'rb') as inn,\
273 open(dst, 'wb') as out:
274 for line in inn:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400275 out.write(line.rstrip() + b'\n')
terryjreedy188aedf2017-06-13 21:32:16 -0400276 print(f'{src} copied to {dst}')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400277
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400278def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400279 "Create HelpWindow; called from Idle Help event handler."
280 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400281 if not isfile(filename):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400282 # try copy_strip, present message
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400283 return
Terry Jan Reedy39e9af62016-08-25 20:04:14 -0400284 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400285
286if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -0400287 from unittest import main
288 main('idlelib.idle_test.test_help', verbosity=2, exit=False)
289
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400290 from idlelib.idle_test.htest import run
291 run(show_idlehelp)