blob: a7008e94ed0514dc23b50654b1ce1f2e90b0c8b2 [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
7<to be moved here from aboutDialog.py>
8
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
28from os.path import abspath, dirname, isdir, isfile, join
Terry Jan Reedy39e9af62016-08-25 20:04:14 -040029from platform import python_version
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040030from tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
31from tkinter import font as tkfont
Terry Jan Reedy52736dd2015-09-25 00:49:18 -040032from idlelib.configHandler import idleConf
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040033
34use_ttk = False # until available to import
35if use_ttk:
36 from tkinter.ttk import Menubutton
37
38## About IDLE ##
39
40
41## IDLE Help ##
42
43class HelpParser(HTMLParser):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -040044 """Render help.html into a text widget.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040045
46 The overridden handle_xyz methods handle a subset of html tags.
47 The supplied text should have the needed tag configurations.
48 The behavior for unsupported tags, such as table, is undefined.
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -050049 If the tags generated by Sphinx change, this class, especially
50 the handle_starttag and handle_endtags methods, might have to also.
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040051 """
52 def __init__(self, text):
53 HTMLParser.__init__(self, convert_charrefs=True)
54 self.text = text # text widget we're rendering into
Terry Jan Reedy974a2712015-09-24 17:32:01 -040055 self.tags = '' # current block level text tags to apply
56 self.chartags = '' # current character level text tags
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040057 self.show = False # used so we exclude page navigation
58 self.hdrlink = False # used so we don't show header links
59 self.level = 0 # indentation level
60 self.pre = False # displaying preformatted text
Terry Jan Reedy28670d12015-09-27 04:40:08 -040061 self.hprefix = '' # prefix such as '25.5' to strip from headings
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040062 self.nested_dl = False # if we're in a nested <dl>
63 self.simplelist = False # simple list (no double spacing)
Terry Jan Reedy28670d12015-09-27 04:40:08 -040064 self.toc = [] # pair headers with text indexes for toc
65 self.header = '' # text within header tags for toc
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
82 elif tag == 'p' and class_ != 'first':
83 s = '\n\n'
84 elif tag == 'span' and class_ == 'pre':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040085 self.chartags = 'pre'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040086 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040087 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040088 elif tag == 'em':
Terry Jan Reedy974a2712015-09-24 17:32:01 -040089 self.chartags = 'em'
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -040090 elif tag in ['ul', 'ol']:
91 if class_.find('simple') != -1:
92 s = '\n'
93 self.simplelist = True
94 else:
95 self.simplelist = False
96 self.indent()
97 elif tag == 'dl':
98 if self.level > 0:
99 self.nested_dl = True
100 elif tag == 'li':
101 s = '\n* ' if self.simplelist else '\n\n* '
102 elif tag == 'dt':
103 s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
104 self.nested_dl = False
105 elif tag == 'dd':
106 self.indent()
107 s = '\n'
108 elif tag == 'pre':
109 self.pre = True
110 if self.show:
111 self.text.insert('end', '\n\n')
112 self.tags = 'preblock'
113 elif tag == 'a' and class_ == 'headerlink':
114 self.hdrlink = True
115 elif tag == 'h1':
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400116 self.tags = tag
117 elif tag in ['h2', 'h3']:
118 if self.show:
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400119 self.header = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400120 self.text.insert('end', '\n\n')
121 self.tags = tag
122 if self.show:
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400123 self.text.insert('end', s, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400124
125 def handle_endtag(self, tag):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400126 "Handle endtags in help.html."
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400127 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400128 self.indent(0) # clear tag, reset indent
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400129 if self.show:
130 self.toc.append((self.header, self.text.index('insert')))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400131 elif tag in ['span', 'em']:
132 self.chartags = ''
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400133 elif tag == 'a':
134 self.hdrlink = False
135 elif tag == 'pre':
136 self.pre = False
137 self.tags = ''
138 elif tag in ['ul', 'dd', 'ol']:
139 self.indent(amt=-1)
140
141 def handle_data(self, data):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400142 "Handle date segments in help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400143 if self.show and not self.hdrlink:
144 d = data if self.pre else data.replace('\n', ' ')
145 if self.tags == 'h1':
146 self.hprefix = d[0:d.index(' ')]
147 if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
148 if d[0:len(self.hprefix)] == self.hprefix:
149 d = d[len(self.hprefix):].strip()
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400150 self.header += d
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400151 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400152
153
154class HelpText(Text):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400155 "Display help.html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400156 def __init__(self, parent, filename):
157 "Configure tags and feed file to parser."
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400158 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
159 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
160 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400161 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy52736dd2015-09-25 00:49:18 -0400162 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400163
164 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
165 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
166 self['font'] = (normalfont, 12)
167 self.tag_configure('em', font=(normalfont, 12, 'italic'))
168 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
169 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
170 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy974a2712015-09-24 17:32:01 -0400171 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400172 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
173 borderwidth=1, relief='solid', background='#eeffcc')
174 self.tag_configure('l1', lmargin1=25, lmargin2=25)
175 self.tag_configure('l2', lmargin1=50, lmargin2=50)
176 self.tag_configure('l3', lmargin1=75, lmargin2=75)
177 self.tag_configure('l4', lmargin1=100, lmargin2=100)
178
179 self.parser = HelpParser(self)
180 with open(filename, encoding='utf-8') as f:
181 contents = f.read()
182 self.parser.feed(contents)
183 self['state'] = 'disabled'
184
185 def findfont(self, names):
186 "Return name of first font family derived from names."
187 for name in names:
188 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
189 font = tkfont.Font(name=name, exists=True, root=self)
190 return font.actual()['family']
191 elif name.lower() in (x.lower()
192 for x in tkfont.families(root=self)):
193 return name
194
195
196class HelpFrame(Frame):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400197 "Display html text, scrollbar, and toc."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400198 def __init__(self, parent, filename):
199 Frame.__init__(self, parent)
200 text = HelpText(self, filename)
201 self['background'] = text['background']
202 scroll = Scrollbar(self, command=text.yview)
203 text['yscrollcommand'] = scroll.set
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400204 self.rowconfigure(0, weight=1)
205 self.columnconfigure(1, weight=1) # text
206 self.toc_menu(text).grid(column=0, row=0, sticky='nw')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400207 text.grid(column=1, row=0, sticky='nsew')
208 scroll.grid(column=2, row=0, sticky='ns')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400209
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400210 def toc_menu(self, text):
211 "Create table of contents as drop-down menu."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400212 toc = Menubutton(self, text='TOC')
213 drop = Menu(toc, tearoff=False)
Terry Jan Reedy28670d12015-09-27 04:40:08 -0400214 for lbl, dex in text.parser.toc:
215 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400216 toc['menu'] = drop
217 return toc
218
219
220class HelpWindow(Toplevel):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400221 "Display frame with rendered html."
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400222 def __init__(self, parent, filename, title):
223 Toplevel.__init__(self, parent)
224 self.wm_title(title)
225 self.protocol("WM_DELETE_WINDOW", self.destroy)
226 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
227 self.grid_columnconfigure(0, weight=1)
228 self.grid_rowconfigure(0, weight=1)
229
230
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400231def copy_strip():
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500232 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500233
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500234 Files with trailing whitespace cannot be pushed to the hg cpython
235 repository. For 3.x (on Windows), help.html is generated, after
236 editing idle.rst in the earliest maintenance version, with
237 sphinx-build -bhtml . build/html
238 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
239 After refreshing TortoiseHG workshop to generate a diff,
240 check both the diff and displayed text. Push the diff along with
241 the idle.rst change and merge both into default (or an intermediate
242 maintenance version).
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500243
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500244 When the 'earlist' version gets its final maintenance release,
245 do an update as described above, without editing idle.rst, to
246 rebase help.html on the next version of idle.rst. Do not worry
247 about version changes as version is not displayed. Examine other
248 changes and the result of Help -> IDLE Help.
Terry Jan Reedy5f582bd2016-03-01 01:18:47 -0500249
Terry Jan Reedy7811a9c2016-03-01 01:13:07 -0500250 If maintenance and default versions of idle.rst diverge, and
251 merging does not go smoothly, then consider generating
252 separate help.html files from separate idle.htmls.
253 """
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400254 src = join(abspath(dirname(dirname(dirname(__file__)))),
255 'Doc', 'build', 'html', 'library', 'idle.html')
256 dst = join(abspath(dirname(__file__)), 'help.html')
257 with open(src, 'rb') as inn,\
258 open(dst, 'wb') as out:
259 for line in inn:
Terry Jan Reedy6f5cdfe2015-09-23 03:45:13 -0400260 out.write(line.rstrip() + b'\n')
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400261 print('idle.html copied to help.html')
262
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400263def show_idlehelp(parent):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400264 "Create HelpWindow; called from Idle Help event handler."
265 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400266 if not isfile(filename):
Terry Jan Reedycba1a1a2015-09-21 22:36:42 -0400267 # try copy_strip, present message
Terry Jan Reedy364d6e12015-09-21 22:42:32 -0400268 return
Terry Jan Reedy39e9af62016-08-25 20:04:14 -0400269 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedy5d46ab12015-09-20 19:57:13 -0400270
271if __name__ == '__main__':
272 from idlelib.idle_test.htest import run
273 run(show_idlehelp)