blob: 3ab4851336b3c9915c457814ddf5b8da3faef241 [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
Terry Jan Reedy3f0222c2016-08-25 20:04:08 -040029from platform import python_version
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040030from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton
31import tkFont as tkfont
Terry Jan Reedy647412f2015-09-25 00:49:02 -040032from idlelib.configHandler import idleConf
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy13b21f82015-09-21 22:36:36 -040044 """Render help.html into a text widget.
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedyd0c21de2016-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 Reedya2f257b2015-09-20 19:56:54 -040051 """
52 def __init__(self, text):
53 HTMLParser.__init__(self)
54 self.text = text # text widget we're rendering into
Terry Jan Reedy49095e22015-09-24 17:31:54 -040055 self.tags = '' # current block level text tags to apply
56 self.chartags = '' # current character level text tags
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy4cdb0542015-09-27 04:40:02 -040061 self.hprefix = '' # prefix such as '25.5' to strip from headings
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040062 self.nested_dl = False # if we're in a nested <dl>
63 self.simplelist = False # simple list (no double spacing)
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -040064 self.toc = [] # pair headers with text indexes for toc
65 self.header = '' # text within header tags for toc
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy13b21f82015-09-21 22:36:36 -040072 "Handle starttags in help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy49095e22015-09-24 17:31:54 -040085 self.chartags = 'pre'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040086 elif tag == 'span' and class_ == 'versionmodified':
Terry Jan Reedy49095e22015-09-24 17:31:54 -040087 self.chartags = 'em'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -040088 elif tag == 'em':
Terry Jan Reedy49095e22015-09-24 17:31:54 -040089 self.chartags = 'em'
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedya2f257b2015-09-20 19:56:54 -0400116 self.tags = tag
117 elif tag in ['h2', 'h3']:
118 if self.show:
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400119 self.header = ''
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400120 self.text.insert('end', '\n\n')
121 self.tags = tag
122 if self.show:
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400123 self.text.insert('end', s, (self.tags, self.chartags))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400124
125 def handle_endtag(self, tag):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400126 "Handle endtags in help.html."
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400127 if tag in ['h1', 'h2', 'h3']:
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400128 self.indent(0) # clear tag, reset indent
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400129 if self.show:
130 self.toc.append((self.header, self.text.index('insert')))
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400131 elif tag in ['span', 'em']:
132 self.chartags = ''
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy13b21f82015-09-21 22:36:36 -0400142 "Handle date segments in help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -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 Reedy4cdb0542015-09-27 04:40:02 -0400150 self.header += d
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400151 self.text.insert('end', d, (self.tags, self.chartags))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400152
153 def handle_charref(self, name):
Terry Jan Reedy558c0de2016-08-25 01:21:54 -0400154 if self.show:
155 self.text.insert('end', unichr(int(name)))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400156
157
158class HelpText(Text):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400159 "Display help.html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400160 def __init__(self, parent, filename):
161 "Configure tags and feed file to parser."
Terry Jan Reedy647412f2015-09-25 00:49:02 -0400162 uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
163 uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
164 uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400165 Text.__init__(self, parent, wrap='word', highlightthickness=0,
Terry Jan Reedy647412f2015-09-25 00:49:02 -0400166 padx=5, borderwidth=0, width=uwide, height=uhigh)
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400167
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400168 normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
169 fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
170 self['font'] = (normalfont, 12)
171 self.tag_configure('em', font=(normalfont, 12, 'italic'))
172 self.tag_configure('h1', font=(normalfont, 20, 'bold'))
173 self.tag_configure('h2', font=(normalfont, 18, 'bold'))
174 self.tag_configure('h3', font=(normalfont, 15, 'bold'))
Terry Jan Reedy49095e22015-09-24 17:31:54 -0400175 self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400176 self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
177 borderwidth=1, relief='solid', background='#eeffcc')
178 self.tag_configure('l1', lmargin1=25, lmargin2=25)
179 self.tag_configure('l2', lmargin1=50, lmargin2=50)
180 self.tag_configure('l3', lmargin1=75, lmargin2=75)
181 self.tag_configure('l4', lmargin1=100, lmargin2=100)
182
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400183 self.parser = HelpParser(self)
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400184 with open(filename) as f:
185 contents = f.read().decode(encoding='utf-8')
186 self.parser.feed(contents)
187 self['state'] = 'disabled'
188
189 def findfont(self, names):
190 "Return name of first font family derived from names."
191 for name in names:
192 if name.lower() in (x.lower() for x in tkfont.names(root=self)):
193 font = tkfont.Font(name=name, exists=True, root=self)
194 return font.actual()['family']
195 elif name.lower() in (x.lower()
196 for x in tkfont.families(root=self)):
197 return name
198
199
200class HelpFrame(Frame):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400201 "Display html text, scrollbar, and toc."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400202 def __init__(self, parent, filename):
203 Frame.__init__(self, parent)
204 text = HelpText(self, filename)
205 self['background'] = text['background']
206 scroll = Scrollbar(self, command=text.yview)
207 text['yscrollcommand'] = scroll.set
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400208 self.rowconfigure(0, weight=1)
209 self.columnconfigure(1, weight=1) # text
210 self.toc_menu(text).grid(column=0, row=0, sticky='nw')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400211 text.grid(column=1, row=0, sticky='nsew')
212 scroll.grid(column=2, row=0, sticky='ns')
Terry Jan Reedy37ad7962015-09-20 20:05:51 -0400213
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400214 def toc_menu(self, text):
215 "Create table of contents as drop-down menu."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400216 toc = Menubutton(self, text='TOC')
217 drop = Menu(toc, tearoff=False)
Terry Jan Reedy4cdb0542015-09-27 04:40:02 -0400218 for lbl, dex in text.parser.toc:
219 drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400220 toc['menu'] = drop
221 return toc
222
223
224class HelpWindow(Toplevel):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400225 "Display frame with rendered html."
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400226 def __init__(self, parent, filename, title):
227 Toplevel.__init__(self, parent)
228 self.wm_title(title)
229 self.protocol("WM_DELETE_WINDOW", self.destroy)
230 HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
231 self.grid_columnconfigure(0, weight=1)
232 self.grid_rowconfigure(0, weight=1)
233
234
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400235def copy_strip():
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500236 """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400237
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500238 Files with trailing whitespace cannot be pushed to the hg cpython
239 repository. For 3.x (on Windows), help.html is generated, after
240 editing idle.rst in the earliest maintenance version, with
241 sphinx-build -bhtml . build/html
242 python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
243 After refreshing TortoiseHG workshop to generate a diff,
244 check both the diff and displayed text. Push the diff along with
245 the idle.rst change and merge both into default (or an intermediate
246 maintenance version).
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400247
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500248 When the 'earlist' version gets its final maintenance release,
249 do an update as described above, without editing idle.rst, to
250 rebase help.html on the next version of idle.rst. Do not worry
251 about version changes as version is not displayed. Examine other
252 changes and the result of Help -> IDLE Help.
Terry Jan Reedy9d9d99c2016-03-20 20:39:26 -0400253
Terry Jan Reedyd0c21de2016-03-01 01:13:07 -0500254 If maintenance and default versions of idle.rst diverge, and
255 merging does not go smoothly, then consider generating
256 separate help.html files from separate idle.htmls.
257 """
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400258 src = join(abspath(dirname(dirname(dirname(__file__)))),
259 'Doc', 'build', 'html', 'library', 'idle.html')
260 dst = join(abspath(dirname(__file__)), 'help.html')
261 with open(src, 'r') as inn,\
262 open(dst, 'w') as out:
263 for line in inn:
264 out.write(line.rstrip() + '\n')
265 print('idle.html copied to help.html')
266
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400267def show_idlehelp(parent):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400268 "Create HelpWindow; called from Idle Help event handler."
269 filename = join(abspath(dirname(__file__)), 'help.html')
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400270 if not isfile(filename):
Terry Jan Reedy13b21f82015-09-21 22:36:36 -0400271 # try copy_strip, present message
Terry Jan Reedy1234fd92015-09-21 22:42:17 -0400272 return
Terry Jan Reedy3f0222c2016-08-25 20:04:08 -0400273 HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
Terry Jan Reedya2f257b2015-09-20 19:56:54 -0400274
275if __name__ == '__main__':
276 from idlelib.idle_test.htest import run
277 run(show_idlehelp)