Revision history  [back]

Okay, could you help me take a look at this plugin I wrote? It works fine, but there is one issue. For example, if the header information has 5 lines, when the scrollbar scrolls to exactly show the 5th line, activating the plugin to update the header information will cause the scrollbar to automatically scroll to display the entire header, instead of staying at the 5th line position. If the plugin is activated to update when the header is not visible, there is no problem.

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
######################################################################
########################### yang's header ############################
######################################################################
# File:                                                autoheader.py #
# Create Data:                                   2026/02/28 11:22:14 #
# Author:                                                       xxxx #
# Email:                                            xxxx@hotmail.com #
# Last Modified:                                 2026/05/22 11:52:19 #
# Modified By:                                                 xxxXx #
# Copyright (c) 2025 xxxx Culture Media Co., Ltd                     #
######################################################################


import wingapi
import copy
import datetime
import platform
import subprocess
from pathlib import Path

app = wingapi.gApplication
_plugin_id = [None]
_preSaveId = [None]
_fileType = ['.py']


class header():
    def __init__(self, doc):
        # chars
        self.commentChar = "#"
        self.aroundChar = " "
        self.filler = " "  # space
        # attributes
        self.docObj = doc
        self.title = "Comment"
        self.fileNameLabel = "File:"
        self.fileName = ""
        self.fullFileName = ""
        self.emailLabel = "Email:"
        self.email = ""
        self.createTimeLabel = "Create Data:"
        self.modifyDataLabel = "Last Modified:"
        self.modifyByLabel = "Modified By:"
        self.modifyBy = ""
        self.authorLabel = "Author:"
        self.author = ""
        self.copyRightLine = ""
        # lines
        self.lines = {"preLines": [], "startLines": [], "titleLines": [], "bodyLines": [], "endLines": [], "postLines": [], }
        self.actualLines = {}
        self.bodyLines = []
        # controls
        self.maxChar = 70  # if less than bodyLine length, use length of bodyLine.
        self.bodyLineMax = self.maxChar
        self.withFullName = False
        self.lastSpaceLineCount = 2
        self.withStartLine = True
        self.withTitleLine = True

    def setheader(self):
        self.updateHeader()
        f = self.docObj.GetAsFileObject()
        charCount = 0
        # check preLines
        for preLine in self.actualLines["preLines"]:
            docLine = f.readline()
            if docLine.rstrip("\n") != preLine:
                print("preLines is different, insert all.")
                self.docObj.InsertChars(0, str(self))
                return True
            charCount += len(docLine)
        # check startLines
        if self.withStartLine:
            for startLine in self.actualLines["startLines"]:
                docLine = f.readline()
                if docLine.rstrip("\n") != startLine:
                    print("startLines is different, insert all.")
                    self.docObj.InsertChars(0, str(self))
                    return True
                charCount += len(docLine)
        # check titleLines
        if self.withTitleLine:
            for titleLine in self.actualLines["titleLines"]:
                docLine = f.readline()
                if docLine.rstrip("\n") != titleLine:
                    print("titleLines is different, insert all.")
                    self.docObj.InsertChars(0, str(self))
                    return True
                charCount += len(docLine)
        # get bodyLines char count
        for bodyLine in self.actualLines["bodyLines"]:
            docLine = f.readline()
            charCount += len(docLine)
        # check endLines
        if self.withStartLine:
            for endLine in self.actualLines["endLines"]:
                docLine = f.readline()
                if docLine.rstrip("\n") != endLine:
                    print("endLines is different, insert all.")
                    self.docObj.InsertChars(0, str(self))
                    return True
                charCount += len(docLine)
        # find postLine
        for postLine in self.actualLines["postLines"]:
            docLine = f.readline()
            if docLine != postLine:
                print("postLine is different, insert all.")
                self.docObj.InsertChars(0, str(self))
                return True
            charCount += len(docLine)
        # find last space line
        spaceLines = ""
        for i in range(self.lastSpaceLineCount):
            docLine = f.readline()
            if docLine != "\n":
                spaceLines += "\n"
        if spaceLines:
            print("insert spaceLine under the postLine.")
            self.docObj.InsertChars(charCount, spaceLines)
            return True
        self.docObj.DeleteChars(0, charCount - 1 + self.lastSpaceLineCount)
        self.docObj.InsertChars(0, str(self))

    def addLine(self, label, line):
        if label in self.lines:
            self.lines[label].append(line)

    def updateHeader(self):
        createTime = ""
        modifyTime = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
        if platform.system() in ["Linux", "Darwin"]:
            p = subprocess.run(["stat", "-c", "%W", self.docObj.GetFilename()], capture_output=True, text=True)
            createTime = datetime.datetime.fromtimestamp(int(p.stdout)).strftime("%Y/%m/%d %H:%M:%S")
        elif platform.system() in ["Windows"]:
            createTime = datetime.datetime.fromtimestamp(Path(self.docObj.GetFilename()).st_ctime).strftime("%Y/%m/%d %H:%M:%S")
        self.actualLines = copy.deepcopy(self.lines)
        # comment character
        if self.commentChar and isinstance(self.commentChar, str):
            self.commentChar = self.commentChar[0]
        else:
            print("commentChar error.")
            return 0
        # around character
        if self.aroundChar and isinstance(self.aroundChar, str):
            self.aroundChar = self.aroundChar[0]

        # bodyLines
        if self.withFullName:
            self.bodyLines.append([self.commentChar, self.fileNameLabel, '', self.fullFileName, self.commentChar])
        else:
            self.bodyLines.append([self.commentChar, self.fileNameLabel, '', self.fileName, self.commentChar])
        self.bodyLines.append([self.commentChar, self.createTimeLabel, '', createTime, self.commentChar])
        self.bodyLines.append([self.commentChar, self.authorLabel, '', self.author, self.commentChar])
        self.bodyLines.append([self.commentChar, self.emailLabel, '', self.email, self.commentChar])
        self.bodyLines.append([self.commentChar, self.modifyDataLabel, '', modifyTime, self.commentChar])
        self.bodyLines.append([self.commentChar, self.modifyByLabel, '', self.modifyBy, self.commentChar])
        self.bodyLineMax = max([len(self.aroundChar.join(line)) for line in self.bodyLines])
        if self.maxChar < self.bodyLineMax:
            self.maxChar = self.bodyLineMax

        # startLine
        if self.withStartLine:
            self.actualLines["startLines"].append(self.commentChar * self.maxChar)

        # titleLine
        if self.withTitleLine and self.title:
            self.actualLines["titleLines"].append((self.aroundChar + self.title + self.aroundChar).center(self.maxChar, self.commentChar))
            self.actualLines["titleLines"].append(self.commentChar * self.maxChar)

        # bodylines
        for line in self.bodyLines:
            line.insert(3, self.filler * (self.maxChar - len(self.aroundChar.join(line)) - 1))
            self.actualLines["bodyLines"].append(self.aroundChar.join(line))

        # copyright
        step = self.maxChar - 2 * len(self.commentChar) - 2 * len(self.aroundChar)
        tmpLines = []
        for i in range(0, len(self.copyRightLine), step):
            tmpLines.append([self.commentChar, self.copyRightLine[i:i + step].ljust(step, self.filler), self.commentChar])
        for line in tmpLines:
            line[1] += self.filler * (step - len(line[1]))
            self.actualLines["bodyLines"].append(self.aroundChar.join(line))

        # endline
        if self.withStartLine:
            self.actualLines["endLines"].append(self.commentChar * self.maxChar)

    def __str__(self):
        result = ""
        for key in self.actualLines:
            if self.actualLines[key]:
                result += "\n".join(self.actualLines[key]) + "\n"
        result += "\n" * self.lastSpaceLineCount
        return result

    def __repr__(self):
        return self.__str__


def _activetor_ah(plugin_id):
    _plugin_id[0] = plugin_id
    return True


_plugin = ["AutoHeaderPlugin", _activetor_ah]


def _connect(doc):
    if Path(doc.GetFilename()).suffix in _fileType:
        doc.Connect('presave', autoheader)


def autoheader(*args, **kwargs):
    h = header(app.GetActiveDocument())
    if h.docObj and Path(h.docObj.GetFilename()).suffix in _fileType:
        h.fullFileName = h.docObj.GetFilename()
        h.fileName = Path(h.docObj.GetFilename()).name
        h.title = "yang's header"
        h.author = "xxxx"
        h.modifyBy = "xxxXx"
        h.email = "xxxx@hotmail.com"
        h.addLine("preLines", "#!/usr/bin/env python3")
        h.addLine("preLines", "# -*- coding:utf-8 -*-")
        h.copyRightLine = "Copyright (c) 2025 xxxx Culture Media Co., Ltd"
        # print('update header for {}'.format(h.fileName))
        h.docObj.BeginUndoAction()
        try:
            h.setheader()
        finally:
            h.docObj.EndUndoAction()


autoheader.label = "Auto Header"
autoheader.contexts = [wingapi.kContextNewMenu("Scripts"),
                       wingapi.kContextEditor(),
                       ]

app.Connect("document-open", _connect)
# def _enablePlugin(editor):
# fn = Path(editor.GetDocument().GetFilename())
# if fn.suffix == '.py':
# app.EnablePlugin(_plugin_id[0], True)
# _preSaveId[0] = app.GetActiveDocument().Connect("presave", autoheader)
# else:
# app.EnablePlugin(_plugin_id[0], False)
# app.GetActiveDocument().Disconnect(_preSaveId[0])

# app.Connect("active-editor-changed", _enablePlugin)