|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
- import tkinter as tk
- from tkinter import scrolledtext, filedialog, messagebox
- import re
- import tempfile
- import webbrowser
- import os
- class HTMLCompiler:
- def __init__(self, root):
- self.root = root
- self.root.title("HTML 编译器")
- self.root.configure(bg='black')
- self.current_file = None
-
- # 创建菜单栏
- self.menu_bar = tk.Menu(root)
-
- # 文件菜单
- self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
- self.file_menu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
- self.file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
- self.file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
- self.file_menu.add_command(label="另存为", command=self.save_as_file)
- self.file_menu.add_separator()
- self.file_menu.add_command(label="退出", command=self.root.quit)
- self.menu_bar.add_cascade(label="文件", menu=self.file_menu)
-
- # 运行菜单
- self.run_menu = tk.Menu(self.menu_bar, tearoff=0)
- self.run_menu.add_command(label="运行HTML", command=self.run_html, accelerator="F5")
- self.menu_bar.add_cascade(label="运行", menu=self.run_menu)
-
- self.root.config(menu=self.menu_bar)
-
- # 创建工具栏
- self.toolbar = tk.Frame(root, bg='#333')
-
- self.new_btn = tk.Button(self.toolbar, text="新建", command=self.new_file, bg='#333', fg='white')
- self.open_btn = tk.Button(self.toolbar, text="打开", command=self.open_file, bg='#333', fg='white')
- self.save_btn = tk.Button(self.toolbar, text="保存", command=self.save_file, bg='#333', fg='white')
- self.run_btn = tk.Button(self.toolbar, text="运行", command=self.run_html, bg='#333', fg='white')
-
- self.new_btn.pack(side=tk.LEFT, padx=2, pady=2)
- self.open_btn.pack(side=tk.LEFT, padx=2, pady=2)
- self.save_btn.pack(side=tk.LEFT, padx=2, pady=2)
- self.run_btn.pack(side=tk.LEFT, padx=2, pady=2)
-
- self.toolbar.pack(fill=tk.X)
-
- # 创建文本编辑器
- self.text_area = scrolledtext.ScrolledText(
- root, wrap=tk.WORD, bg='black', fg='white',
- insertbackground='white', font=('Consolas', 12)
- )
- self.text_area.pack(fill=tk.BOTH, expand=True)
-
- # 绑定事件
- self.text_area.bind('<KeyRelease>', self.on_key_release)
- self.text_area.bind('<Tab>', self.handle_tab)
- self.text_area.bind('<Return>', self.handle_return)
- self.text_area.bind('<KeyPress>', self.handle_key_press)
-
- # 绑定快捷键
- self.root.bind('<Control-n>', lambda event: self.new_file())
- self.root.bind('<Control-o>', lambda event: self.open_file())
- self.root.bind('<Control-s>', lambda event: self.save_file())
- self.root.bind('<F5>', lambda event: self.run_html())
-
- # 自动补全相关
- self.autocomplete_window = None
- self.autocomplete_listbox = None
- self.autocomplete_pos = None
- self.autocomplete_words = [
- '<html>', '</html>', '<head>', '</head>', '<body>', '</body>',
- '<title>', '</title>', '<div>', '</div>', '<p>', '</p>',
- '<h1>', '</h1>', '<h2>', '</h2>', '<h3>', '</h3>',
- '<a>', '</a>', '<img>', '<ul>', '</ul>', '<li>', '</li>',
- '<table>', '</table>', '<tr>', '</tr>', '<td>', '</td>',
- '<style>', '</style>', '<script>', '</script>', '<span>', '</span>',
- '<br>', '<hr>', '<input>', '<button>', '</button>', '<form>', '</form>'
- ]
-
- # 语法高亮颜色配置
- self.tag_colors = {
- 'tag': '#569CD6',
- 'attribute': '#9CDCFE',
- 'value': '#CE9178',
- 'comment': '#6A9955',
- 'doctype': '#569CD6',
- 'string': '#CE9178',
- 'symbol': '#D4D4D4'
- }
-
- # 配置标签样式
- for tag, color in self.tag_colors.items():
- self.text_area.tag_config(tag, foreground=color)
-
- # 初始高亮
- self.highlight()
-
- # 新建一个空白文件
- self.new_file()
-
- # 文件操作功能
- def new_file(self, event=None):
- self.text_area.delete('1.0', tk.END)
- self.current_file = None
- self.root.title("HTML 编译器 - 未命名")
-
- def open_file(self, event=None):
- file_path = filedialog.askopenfilename(
- filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
- )
- if file_path:
- try:
- with open(file_path, 'r', encoding='utf-8') as file:
- content = file.read()
- self.text_area.delete('1.0', tk.END)
- self.text_area.insert('1.0', content)
- self.current_file = file_path
- self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
- self.highlight()
- except Exception as e:
- messagebox.showerror("错误", f"无法打开文件:\n{str(e)}")
-
- def save_file(self, event=None):
- if self.current_file:
- try:
- with open(self.current_file, 'w', encoding='utf-8') as file:
- file.write(self.text_area.get('1.0', tk.END))
- messagebox.showinfo("保存", "文件保存成功!")
- except Exception as e:
- messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
- else:
- self.save_as_file()
-
- def save_as_file(self):
- file_path = filedialog.asksaveasfilename(
- defaultextension=".html",
- filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
- )
- if file_path:
- try:
- with open(file_path, 'w', encoding='utf-8') as file:
- file.write(self.text_area.get('1.0', tk.END))
- self.current_file = file_path
- self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
- messagebox.showinfo("保存", "文件保存成功!")
- except Exception as e:
- messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
-
- def run_html(self, event=None):
- html_code = self.text_area.get("1.0", tk.END)
-
- if not html_code.strip():
- messagebox.showwarning("警告", "没有内容可运行!")
- return
-
- # 创建临时HTML文件
- with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html') as f:
- f.write(html_code)
- temp_path = f.name
-
- # 用默认浏览器打开
- try:
- webbrowser.open('file://' + os.path.abspath(temp_path))
- except Exception as e:
- messagebox.showerror("错误", f"无法运行HTML:\n{str(e)}")
-
- # 以下是原有的编辑器功能(自动补全、语法高亮等)
- def on_key_release(self, event):
- self.highlight()
- self.check_autocomplete(event)
-
- def handle_key_press(self, event):
- if event.keysym == 'Escape' and self.autocomplete_window:
- self.autocomplete_window.destroy()
- self.autocomplete_window = None
- return 'break'
-
- if self.autocomplete_window and event.keysym in ('Return', 'Tab', 'Down', 'Up'):
- return self.handle_autocomplete_key(event)
-
- if event.keysym == 'BackSpace':
- self.handle_backspace(event)
-
- def handle_backspace(self, event):
- current_pos = self.text_area.index(tk.INSERT)
- line, col = map(int, current_pos.split('.'))
- line_text = self.text_area.get(f'{line}.0', f'{line}.end')
-
- if col > 0 and line_text[:col].isspace():
- prev_non_space = col - 1
- while prev_non_space >= 0 and line_text[prev_non_space].isspace():
- prev_non_space -= 1
-
- if prev_non_space == -1:
- space_count = col
- if space_count % 4 != 0:
- new_col = (space_count // 4) * 4
- self.text_area.delete(f'{line}.{new_col}', current_pos)
- return 'break'
- return None
-
- def handle_tab(self, event):
- current_pos = self.text_area.index(tk.INSERT)
- line, col = map(int, current_pos.split('.'))
-
- space_count = 4 - (col % 4)
- self.text_area.insert(tk.INSERT, ' ' * space_count)
- return 'break'
-
- def handle_return(self, event):
- current_pos = self.text_area.index(tk.INSERT)
- line, col = map(int, current_pos.split('.'))
- line_text = self.text_area.get(f'{line}.0', f'{line}.end')
-
- indent = 0
- while indent < len(line_text) and line_text[indent] == ' ':
- indent += 1
-
- self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
-
- if line_text.strip().startswith('<') and not line_text.strip().startswith('</'):
- match = re.match(r'^\s*<([a-zA-Z]+)[^>]*>', line_text)
- if match and not line_text.strip().endswith('/>'):
- tag = match.group(1)
- self.text_area.insert(tk.INSERT, f'\n{" " * indent}</{tag}>')
- self.text_area.mark_set(tk.INSERT, f'{line+1}.{indent+1}')
-
- return 'break'
-
- def check_autocomplete(self, event):
- if self.autocomplete_window:
- self.autocomplete_window.destroy()
- self.autocomplete_window = None
-
- current_pos = self.text_area.index(tk.INSERT)
- line, col = map(int, current_pos.split('.'))
- line_text = self.text_area.get(f'{line}.0', current_pos)
-
- if line_text and (line_text[-1].isalpha() or line_text[-1] == '<' or line_text[-1] == '/'):
- word_start = max(line_text.rfind(' '), line_text.rfind('<'), line_text.rfind('>'), line_text.rfind('"')) + 1
- current_word = line_text[word_start:]
-
- matches = [w for w in self.autocomplete_words if w.startswith(current_word.lower())]
-
- if matches:
- self.show_autocomplete(matches, current_pos, word_start, col)
-
- def show_autocomplete(self, matches, current_pos, word_start, col):
- line, _ = map(int, current_pos.split('.'))
-
- self.autocomplete_window = tk.Toplevel(self.root)
- self.autocomplete_window.wm_overrideredirect(True)
- self.autocomplete_window.wm_geometry("+%d+%d" % (
- self.root.winfo_rootx() + self.text_area.winfo_x() +
- self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
- 'xpixels', 'ypixels')[0],
- self.root.winfo_rooty() + self.text_area.winfo_y() +
- self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
- 'xpixels', 'ypixels')[1] + 20
- ))
-
- self.autocomplete_listbox = tk.Listbox(
- self.autocomplete_window,
- height=min(10, len(matches)),
- bg='#2D2D2D', fg='white',
- selectbackground='#3D3D3D',
- font=('Consolas', 12)
- )
- self.autocomplete_listbox.pack()
-
- for match in matches:
- self.autocomplete_listbox.insert(tk.END, match)
-
- self.autocomplete_listbox.bind('<Return>', self.select_autocomplete)
- self.autocomplete_listbox.bind('<Tab>', self.select_autocomplete)
- self.autocomplete_listbox.bind('<Escape>', lambda e: self.autocomplete_window.destroy())
- self.autocomplete_listbox.bind('<Button-1>', self.select_autocomplete)
-
- self.autocomplete_pos = (line, word_start, col)
-
- self.autocomplete_listbox.focus_set()
- self.autocomplete_listbox.selection_set(0)
-
- def handle_autocomplete_key(self, event):
- if not self.autocomplete_listbox:
- return
-
- selection = self.autocomplete_listbox.curselection()
-
- if event.keysym == 'Return' or event.keysym == 'Tab':
- if selection:
- self.select_autocomplete(event)
- return 'break'
- elif event.keysym == 'Down':
- if selection and selection[0] < self.autocomplete_listbox.size() - 1:
- self.autocomplete_listbox.selection_clear(selection[0])
- self.autocomplete_listbox.selection_set(selection[0] + 1)
- return 'break'
- elif event.keysym == 'Up':
- if selection and selection[0] > 0:
- self.autocomplete_listbox.selection_clear(selection[0])
- self.autocomplete_listbox.selection_set(selection[0] - 1)
- return 'break'
-
- return None
-
- def select_autocomplete(self, event):
- if not self.autocomplete_listbox:
- return
-
- selection = self.autocomplete_listbox.curselection()
- if selection:
- selected = self.autocomplete_listbox.get(selection[0])
- line, word_start, col = self.autocomplete_pos
-
- self.text_area.delete(f'{line}.{word_start}', f'{line}.{col}')
- self.text_area.insert(f'{line}.{word_start}', selected)
-
- self.text_area.mark_set(tk.INSERT, f'{line}.{word_start + len(selected)}')
-
- self.autocomplete_window.destroy()
- self.autocomplete_window = None
- self.text_area.focus_set()
- return 'break'
-
- def highlight(self):
- for tag in self.tag_colors.keys():
- self.text_area.tag_remove(tag, '1.0', tk.END)
-
- text = self.text_area.get('1.0', tk.END)
-
- # 高亮注释
- self.highlight_pattern(r'<!--.*?-->', 'comment')
-
- # 高亮DOCTYPE
- self.highlight_pattern(r'<!DOCTYPE.*?>', 'doctype')
-
- # 高亮标签
- self.highlight_pattern(r'<\/?[a-zA-Z]+', 'tag')
- self.highlight_pattern(r'<\/[a-zA-Z]+>', 'tag')
-
- # 高亮属性
- self.highlight_pattern(r'\s[a-zA-Z-]+=', 'attribute')
-
- # 高亮属性值
- self.highlight_pattern(r'"[^"]*"', 'value')
- self.highlight_pattern(r"'[^']*'", 'value')
-
- # 高亮符号
- self.highlight_pattern(r'[<>\/=]', 'symbol')
-
- def highlight_pattern(self, pattern, tag):
- start = '1.0'
- end = tk.END
-
- self.text_area.mark_set('matchStart', start)
- self.text_area.mark_set('matchEnd', start)
-
- count = tk.IntVar()
- while True:
- index = self.text_area.search(
- pattern, 'matchEnd', end,
- count=count, regexp=True
- )
- if index == '': break
-
- self.text_area.mark_set('matchStart', index)
- self.text_area.mark_set('matchEnd', f'{index}+{count.get()}c')
-
- self.text_area.tag_add(tag, 'matchStart', 'matchEnd')
- if __name__ == '__main__':
- root = tk.Tk()
- root.geometry('800x600')
- app = HTMLCompiler(root)
- root.mainloop()
复制代码 |
评分
-
查看全部评分
|