blob: 8599b13d9c5d4125a7ec23b03a7c01481ba25203 [file] [log] [blame]
Terry Jan Reedy13b21f82015-09-21 22:36:36 -04001""" help.py: Implement the Idle help menu.
2Contents are subject to revision at any time, without notice.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -04003
Terry Jan Reedya2f257b2015-09-20 19:56:54 -04004
5Help => About IDLE: diplay About Idle dialog
6
7<to be moved here from aboutDialog.py>
8
Terry Jan Reedya2f257b2015-09-20 19:56:54 -04009
Terry Jan Reedy13b21f82015-09-21 22:36:36 -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 Reedya2f257b2015-09-20 19:56:54 -040013
Martin Panter2dafcc22016-02-10 01:17:51 +000014HelpParser - Parse help.html and render to tk Text.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040015
Terry Jan Reedy13b21f82015-09-21 22:36:36 -040016HelpText - Display formatted help.html.
17
18HelpFrame - Contain text, scrollbar, and table-of-contents.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040019(This will be needed for display in a future tabbed window.)
20
Terry Jan Reedy13b21f82015-09-21 22:36:36 -040021HelpWindow - Display HelpFrame in a standalone window.
22
23copy_strip - Copy idle.html to help.html, rstripping each line.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040024
25show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog.
26"""
27from HTMLParser import HTMLParser
28from os.path import abspath, dirname, isdir, isfile, join
29from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
30import tkFont as tkfont
Terry Jan Reedy647412f2015-09-25 00:49:02 -040031from idlelib.configHandler import idleConf
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040032
33use_ttk = False # until available to import
34if use_ttk:
35 from tkinter.ttk import Menubutton
36
37## About IDLE ##
38
39
40## IDLE Help ##
41
42class HelpParser(HTMLParser):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -040043 """Render help.html into a text widget.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedyd0c21de2016-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 Reedya2f257b2015-09-20 19:56:54 -040050 """
51 def __init__(self, text):
52 HTMLParser.__init__(self)
53 self.text = text # text widget we're rendering into
Terry Jan Reedy49095e22015-09-24 17:31:54 -040054 self.tags = '' # current block level text tags to apply
55 self.chartags = '' # current character level text tags
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy4cdb0542015-09-27 04:40:02 -040060 self.hprefix = '' # prefix such as '25.5' to strip from headings
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040061 self.nested_dl = False # if we're in a nested <dl>
62 self.simplelist = False # simple list (no double spacing)
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -040063 self.toc = [] # pair headers with text indexes for toc
64 self.header = '' # text within header tags for toc
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy13b21f82015-09-21 22:36:36 -040071 "Handle starttags in help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy49095e22015-09-24 17:31:54 -040084 self.chartags = 'pre'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040085 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy49095e22015-09-24 17:31:54 -040086 self.chartags = 'em'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040087 elif tag == 'em':
Terry Jan Reedy49095e22015-09-24 17:31:54 -040088 self.chartags = 'em'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedya2f257b2015-09-20 19:56:54 -0400115 self.tags = tag
116 elif tag in ['h2', 'h3']:
117 if self.show:
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400118 self.header = ''
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400119 self.text.insert('end', '\n\n')
120 self.tags = tag
121 if self.show:
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400122 self.text.insert('end', s, (self.tags, self.chartags))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400123
124 def handle_endtag(self, tag):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400125 "Handle endtags in help.html."
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400126 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400127 self.indent(0) # clear tag, reset indent
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400128 if self.show:
129 self.toc.append((self.header, self.text.index('insert')))
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400130 elif tag in ['span', 'em']:
131 self.chartags = ''
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy13b21f82015-09-21 22:36:36 -0400141 "Handle date segments in help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy4cdb0542015-09-27 04:40:02 -0400149 self.header += d
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400150 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400151
152 def handle_charref(self, name):
Terry Jan Reedy558c0de2016-08-25 01:21:54 -0400153 if self.show:
154 self.text.insert('end', unichr(int(name)))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400155
156
157class HelpText(Text):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400158 "Display help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400159 def __init__(self, parent, filename):
160 "Configure tags and feed file to parser."
Terry Jan Reedy647412f2015-09-25 00:49:02 -0400161 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
162 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
163 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400164 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy647412f2015-09-25 00:49:02 -0400165 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400166
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400167 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
168 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
169 self['font'] = (normalfont, 12)
170 self.tag_configure('em', font=(normalfont, 12, 'italic'))
171 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
172 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
173 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400174 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400175 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
176 borderwidth=1, relief='solid', background='#eeffcc')
177 self.tag_configure('l1', lmargin1=25, lmargin2=25)
178 self.tag_configure('l2', lmargin1=50, lmargin2=50)
179 self.tag_configure('l3', lmargin1=75, lmargin2=75)
180 self.tag_configure('l4', lmargin1=100, lmargin2=100)
181
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400182 self.parser = HelpParser(self)
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400183 with open(filename) as f:
184 contents = f.read().decode(encoding='utf-8')
185 self.parser.feed(contents)
186 self['state'] = 'disabled'
187
188 def findfont(self, names):
189 "Return name of first font family derived from names."
190 for name in names:
191 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
192 font = tkfont.Font(name=name, exists=True, root=self)
193 return font.actual()['family']
194 elif name.lower() in (x.lower()
195 for x in tkfont.families(root=self)):
196 return name
197
198
199class HelpFrame(Frame):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400200 "Display html text, scrollbar, and toc."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400201 def __init__(self, parent, filename):
202 Frame.__init__(self, parent)
203 text = HelpText(self, filename)
204 self['background'] = text['background']
205 scroll = Scrollbar(self, command=text.yview)
206 text['yscrollcommand'] = scroll.set
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400207 self.rowconfigure(0, weight=1)
208 self.columnconfigure(1, weight=1) # text
209 self.toc_menu(text).grid(column=0, row=0, sticky='nw')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400210 text.grid(column=1, row=0, sticky='nsew')
211 scroll.grid(column=2, row=0, sticky='ns')
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400212
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400213 def toc_menu(self, text):
214 "Create table of contents as drop-down menu."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400215 toc = Menubutton(self, text='TOC')
216 drop = Menu(toc, tearoff=False)
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400217 for lbl, dex in text.parser.toc:
218 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400219 toc['menu'] = drop
220 return toc
221
222
223class HelpWindow(Toplevel):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400224 "Display frame with rendered html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400225 def __init__(self, parent, filename, title):
226 Toplevel.__init__(self, parent)
227 self.wm_title(title)
228 self.protocol("WM_DELETE_WINDOW", self.destroy)
229 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
230 self.grid_columnconfigure(0, weight=1)
231 self.grid_rowconfigure(0, weight=1)
232
233
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400234def copy_strip():
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500235 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400236
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500237 Files with trailing whitespace cannot be pushed to the hg cpython
238 repository. For 3.x (on Windows), help.html is generated, after
239 editing idle.rst in the earliest maintenance version, with
240 sphinx-build -bhtml . build/html
241 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
242 After refreshing TortoiseHG workshop to generate a diff,
243 check both the diff and displayed text. Push the diff along with
244 the idle.rst change and merge both into default (or an intermediate
245 maintenance version).
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400246
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500247 When the 'earlist' version gets its final maintenance release,
248 do an update as described above, without editing idle.rst, to
249 rebase help.html on the next version of idle.rst. Do not worry
250 about version changes as version is not displayed. Examine other
251 changes and the result of Help -> IDLE Help.
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400252
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500253 If maintenance and default versions of idle.rst diverge, and
254 merging does not go smoothly, then consider generating
255 separate help.html files from separate idle.htmls.
256 """
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400257 src = join(abspath(dirname(dirname(dirname(__file__)))),
258 'Doc', 'build', 'html', 'library', 'idle.html')
259 dst = join(abspath(dirname(__file__)), 'help.html')
260 with open(src, 'r') as inn,\
261 open(dst, 'w') as out:
262 for line in inn:
263 out.write(line.rstrip() + '\n')
264 print('idle.html copied to help.html')
265
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400266def show_idlehelp(parent):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400267 "Create HelpWindow; called from Idle Help event handler."
268 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400269 if not isfile(filename):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400270 # try copy_strip, present message
Terry Jan Reedy1234fd92015-09-21 22:42:17 -0400271 return
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400272 HelpWindow(parent, filename, 'IDLE Help')
273
274if __name__ == '__main__':
275 from idlelib.idle_test.htest import run
276 run(show_idlehelp)