冲浪的口香糖 发表于 2025-7-1 21:26:17

用python内置库做的html编译器(有点小bug)

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.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 += 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*<(+)[^>]*>', 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
            
            matches =
            
            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'),
            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') + 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 < self.autocomplete_listbox.size() - 1:
                self.autocomplete_listbox.selection_clear(selection)
                self.autocomplete_listbox.selection_set(selection + 1)
            return 'break'
      elif event.keysym == 'Up':
            if selection and selection > 0:
                self.autocomplete_listbox.selection_clear(selection)
                self.autocomplete_listbox.selection_set(selection - 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)
            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'<\/?+', 'tag')
      self.highlight_pattern(r'<\/+>', 'tag')
      
      # 高亮属性
      self.highlight_pattern(r'\s+=', '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()

FishC 发表于 2025-7-1 21:27:38

我注意到你的HTML编译器代码有几个小问题,主要涉及自动补全功能未完成和部分逻辑错误。以下是修复建议:

### 主要问题修复:
1. **自动补全列表未定义**:
   需要补全 `self.autocomplete_words` 列表,例如:
   
   self.autocomplete_words = ['<html>', '<head>', '<body>', '<div>', '<p>', '<a>', '<img>', '<script>', '<style>', 'class', 'id', 'href', 'src']
   

2. **文件对话框类型缺失**:
   在 `open_file` 和 `save_as_file` 方法中补全文件类型:
   
   filetypes=[('HTML 文件', '*.html'), ('所有文件', '*.*')]
   

3. **回车缩进逻辑错误**:
   修复 `handle_return` 的缩进计算:
   
   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 += 1
      
       self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
       return 'break'# 阻止默认回车行为
   

### 其他优化建议:
1. **语法高亮增强**:
   在 `highlight` 方法(未提供)中建议使用正则表达式匹配标签:
   re.findall(r'<[^>]+>', content)

2. **临时文件清理**:
   在 `run_html` 中添加延时删除临时文件:
   
   import threading
   import time
   
   def delete_temp_file(path):
       time.sleep(5)# 延迟5秒确保浏览器加载
       os.remove(path)
   
   # 在webbrowser.open后添加
   threading.Thread(target=delete_temp_file, args=(temp_path,)).start()
   

3. **自动补全框架**:
   补全基础自动补全方法(需自行完善匹配逻辑):
   
   def check_autocomplete(self, event):
       # 获取当前单词逻辑示例
       current_pos = self.text_area.index(tk.INSERT)
       words = self.text_area.get("1.0", current_pos).split()
       if not words:
         return
       last_word = words[-1]
      
       # 简单示例:匹配开头字符
       matches =
       if matches:
         self.show_autocomplete(matches, current_pos)
   

### 使用注意事项:
1. 确保语法高亮方法 `highlight` 已实现
2. 自动补全功能需要完善位置计算和选择逻辑
3. Windows系统可能需要调整临时文件路径处理

> 提示:你可以先注释掉自动补全相关的绑定事件(self.text_area.bind('<KeyRelease>', self.on_key_release)),优先确保基础功能运行正常。

需要进一步调试某个功能可以告诉我具体问题点,我会提供更详细的解决方案!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

某一个“天” 发表于 2025-7-1 21:36:38

{:10_256:}

小甲鱼的二师兄 发表于 2025-7-2 00:48:48

我去,这么狠的吗?

小甲鱼的二师兄 发表于 2025-7-2 00:52:50

看了一下,这段代码其实是用 tkinter 做了一个简单的 HTML 编辑器,能新建、打开、保存、运行 HTML 文件,还带了点语法高亮和自动补全,操作起来比较方便。

下次发帖配点图片那就更好了,加油~

{:13_450:}
页: [1]
查看完整版本: 用python内置库做的html编译器(有点小bug)