blob: 21b5ea5a816e3fc2a642d9bcabcedb7c403a89bf [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
5Help => About IDLE: diplay About Idle dialog
6
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
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040065
66 def indent(self, amt=1):
67 self.level += amt
68 self.tags = '' if self.level == 0 else 'l'+str(self.level)
69
70 def handle_starttag(self, tag, attrs):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040071 "Handle starttags in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040072 class_ = ''
73 for a, v in attrs:
74 if a == 'class':
75 class_ = v
76 s = ''
77 if tag == 'div' and class_ == 'section':
78 self.show = True # start of main content
79 elif tag == 'div' and class_ == 'sphinxsidebar':
80 self.show = False # end of main content
81 elif tag == 'p' and class_ != 'first':
82 s = '\n\n'
83 elif tag == 'span' and class_ == 'pre':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040084 self.chartags = 'pre'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040085 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040086 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040087 elif tag == 'em':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040088 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040089 elif tag in ['ul', 'ol']:
90 if class_.find('simple') != -1:
91 s = '\n'
92 self.simplelist = True
93 else:
94 self.simplelist = False
95 self.indent()
96 elif tag == 'dl':
97 if self.level > 0:
98 self.nested_dl = True
99 elif tag == 'li':
100 s = '\n* ' if self.simplelist else '\n\n* '
101 elif tag == 'dt':
102 s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
103 self.nested_dl = False
104 elif tag == 'dd':
105 self.indent()
106 s = '\n'
107 elif tag == 'pre':
108 self.pre = True
109 if self.show:
110 self.text.insert('end', '\n\n')
111 self.tags = 'preblock'
112 elif tag == 'a' and class_ == 'headerlink':
113 self.hdrlink = True
114 elif tag == 'h1':
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400115 self.tags = tag
116 elif tag in ['h2', 'h3']:
117 if self.show:
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400118 self.header = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400119 self.text.insert('end', '\n\n')
120 self.tags = tag
121 if self.show:
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400122 self.text.insert('end', s, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400123
124 def handle_endtag(self, tag):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400125 "Handle endtags in help.html."
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400126 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400127 self.indent(0) # clear tag, reset indent
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400128 if self.show:
129 self.toc.append((self.header, self.text.index('insert')))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400130 elif tag in ['span', 'em']:
131 self.chartags = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400132 elif tag == 'a':
133 self.hdrlink = False
134 elif tag == 'pre':
135 self.pre = False
136 self.tags = ''
137 elif tag in ['ul', 'dd', 'ol']:
138 self.indent(amt=-1)
139
140 def handle_data(self, data):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400141 "Handle date segments in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400142 if self.show and not self.hdrlink:
143 d = data if self.pre else data.replace('\n', ' ')
144 if self.tags == 'h1':
145 self.hprefix = d[0:d.index(' ')]
146 if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
147 if d[0:len(self.hprefix)] == self.hprefix:
148 d = d[len(self.hprefix):].strip()
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400149 self.header += d
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400150 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400151
152
153class HelpText(Text):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400154 "Display help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400155 def __init__(self, parent, filename):
156 "Configure tags and feed file to parser."
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400157 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
158 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
159 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400160 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400161 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400162
163 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
164 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
165 self['font'] = (normalfont, 12)
166 self.tag_configure('em', font=(normalfont, 12, 'italic'))
167 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
168 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
169 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400170 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400171 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
172 borderwidth=1, relief='solid', background='#eeffcc')
173 self.tag_configure('l1', lmargin1=25, lmargin2=25)
174 self.tag_configure('l2', lmargin1=50, lmargin2=50)
175 self.tag_configure('l3', lmargin1=75, lmargin2=75)
176 self.tag_configure('l4', lmargin1=100, lmargin2=100)
177
178 self.parser = HelpParser(self)
179 with open(filename, encoding='utf-8') as f:
180 contents = f.read()
181 self.parser.feed(contents)
182 self['state'] = 'disabled'
183
184 def findfont(self, names):
185 "Return name of first font family derived from names."
186 for name in names:
187 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
188 font = tkfont.Font(name=name, exists=True, root=self)
189 return font.actual()['family']
190 elif name.lower() in (x.lower()
191 for x in tkfont.families(root=self)):
192 return name
193
194
195class HelpFrame(Frame):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400196 "Display html text, scrollbar, and toc."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400197 def __init__(self, parent, filename):
198 Frame.__init__(self, parent)
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400199 # keep references to widgets for test access.
200 self.text = text = HelpText(self, filename)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400201 self['background'] = text['background']
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400202 self.toc = toc = self.toc_menu(text)
203 self.scroll = scroll = Scrollbar(self, command=text.yview)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400204 text['yscrollcommand'] = scroll.set
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400205
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400206 self.rowconfigure(0, weight=1)
207 self.columnconfigure(1, weight=1) # text
Terry Jan Reedy01e35752016-06-10 18:19:21 -0400208 toc.grid(row=0, column=0, sticky='nw')
209 text.grid(row=0, column=1, sticky='nsew')
210 scroll.grid(row=0, column=2, sticky='ns')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400211
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400212 def toc_menu(self, text):
213 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400214 toc = Menubutton(self, text='TOC')
215 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400216 for lbl, dex in text.parser.toc:
217 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400218 toc['menu'] = drop
219 return toc
220
221
222class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400223 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400224 def __init__(self, parent, filename, title):
225 Toplevel.__init__(self, parent)
226 self.wm_title(title)
227 self.protocol("WM_DELETE_WINDOW", self.destroy)
228 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
229 self.grid_columnconfigure(0, weight=1)
230 self.grid_rowconfigure(0, weight=1)
231
232
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400233def copy_strip():
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500234 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500235
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500236 Files with trailing whitespace cannot be pushed to the hg cpython
237 repository. For 3.x (on Windows), help.html is generated, after
238 editing idle.rst in the earliest maintenance version, with
239 sphinx-build -bhtml . build/html
240 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
241 After refreshing TortoiseHG workshop to generate a diff,
242 check both the diff and displayed text. Push the diff along with
243 the idle.rst change and merge both into default (or an intermediate
244 maintenance version).
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500245
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500246 When the 'earlist' version gets its final maintenance release,
247 do an update as described above, without editing idle.rst, to
248 rebase help.html on the next version of idle.rst. Do not worry
249 about version changes as version is not displayed. Examine other
250 changes and the result of Help -> IDLE Help.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500251
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500252 If maintenance and default versions of idle.rst diverge, and
253 merging does not go smoothly, then consider generating
254 separate help.html files from separate idle.htmls.
255 """
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400256 src = join(abspath(dirname(dirname(dirname(__file__)))),
257 'Doc', 'build', 'html', 'library', 'idle.html')
258 dst = join(abspath(dirname(__file__)), 'help.html')
259 with open(src, 'rb') as inn,\
260 open(dst, 'wb') as out:
261 for line in inn:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400262 out.write(line.rstrip() + b'\n')
terryjreedy188aedf2017-06-13 21:32:16 -0400263 print(f'{src} copied to {dst}')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400264
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400265def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400266 "Create HelpWindow; called from Idle Help event handler."
267 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400268 if not isfile(filename):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400269 # try copy_strip, present message
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400270 return
Terry Jan Reedy39e9af62016-08-25 20:04:14 -0400271 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400272
273if __name__ == '__main__':
Terry Jan Reedyee5ef302018-06-15 18:20:55 -0400274 from unittest import main
275 main('idlelib.idle_test.test_help', verbosity=2, exit=False)
276
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400277 from idlelib.idle_test.htest import run
278 run(show_idlehelp)