├── .devcontainer └── devcontainer.json ├── .gitignore ├── Linux ├── Makefile ├── Notepad.png ├── README.md ├── configure ├── dpinstall ├── notepadee.desktop ├── requirements-3.4.txt ├── requirements.txt ├── resources │ └── dist.mak └── src │ └── Notepad==.py ├── README.md ├── Windows ├── Notepad.ico ├── README.md ├── build-3.4.bat ├── build-debug.bat ├── build.bat ├── clean.bat ├── dpinstall-3.4.bat ├── dpinstall.bat ├── notepadee-x64-setup.iss ├── notepadee-x86-setup-is5.iss ├── requirements-3.4.txt ├── requirements.txt └── src │ ├── Notepad.ico │ └── Notepad==.py └── macOS ├── ._.DS_Store ├── README.md ├── autogen.sh └── patches ├── Makefile ├── README.md ├── configure ├── requirements-3.4.txt └── requirements.txt /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/devcontainers/features/desktop-lite:1": {} 5 | }, 6 | "forwardPorts": [6080], 7 | "portsAttributes": { 8 | "6080": { 9 | "label": "desktop" 10 | } 11 | }, 12 | "customizations": { 13 | "codespaces": { 14 | "repositories": { 15 | "matthewyang204/homebrew-formulae-casks": { 16 | "permissions": { 17 | "issues": "write-all" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | # lib/ 18 | # lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc 175 | 176 | # Ignore all .pyc files; they are just annoying cache files created by python 177 | *.pyc 178 | 179 | # Ignore all __pycache__ directories, they are cache folders created by python 180 | __pycache__/ 181 | 182 | # macOS annoyances 183 | .DS_Store 184 | ._* 185 | *DS_Store 186 | 187 | # Nuitka annoyances 188 | nuitka-crash-report.xml 189 | 190 | # GNU nano annoyances 191 | *.swp 192 | *.save 193 | 194 | # Configure files 195 | .configured 196 | 197 | # JetBrains IDE prefs 198 | .idea/ -------------------------------------------------------------------------------- /Linux/Makefile: -------------------------------------------------------------------------------- 1 | # Check for a .configured file generated by configure script 2 | ifeq ($(wildcard .configured),) 3 | $(error You must run `./configure` before you can build.) 4 | endif 5 | 6 | # Variables 7 | APP_NAME = Notepad== 8 | BUILD_DIR = dist 9 | INSTALL_DIR = /opt/matthewyang 10 | BIN_LINK = /usr/local/bin/notepadee 11 | DESKTOP_FILE = /usr/share/applications/notepadee.desktop 12 | VERSION_NUMBER = 5.1.0 13 | 14 | .SILENT: 15 | 16 | all: build 17 | 18 | configure: 19 | ./configure 20 | 21 | build: 22 | pyinstaller --hidden-import=tkinter -i Notepad.png src/$(APP_NAME).py 23 | cp Notepad.png dist/Notepad==/ 24 | touch dist/Notepad==/.pyinstaller 25 | echo "Build completed. To install, run 'sudo make install'" 26 | 27 | install: 28 | sudo mkdir -p $(INSTALL_DIR) 29 | sudo cp -R $(BUILD_DIR)/$(APP_NAME) $(INSTALL_DIR)/ 30 | sudo ln -s $(INSTALL_DIR)/$(APP_NAME)/$(APP_NAME) $(BIN_LINK) 31 | sudo cp notepadee.desktop $(DESKTOP_FILE) 32 | sudo update-desktop-database 33 | echo "Install completed" 34 | 35 | install-no-desktop-file: 36 | sudo mkdir -p $(INSTALL_DIR) 37 | sudo cp -R $(BUILD_DIR)/$(APP_NAME) $(INSTALL_DIR)/ 38 | sudo ln -s $(INSTALL_DIR)/$(APP_NAME)/$(APP_NAME) $(BIN_LINK) 39 | echo "Install completed" 40 | 41 | uninstall: 42 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 43 | sudo rm -f $(BIN_LINK) 44 | sudo rm -rf $(DESKTOP_FILE) 45 | echo "Uninstalled. If issues experienced caused you to uninstall Notepad==, please report them on https://www.github.com/matthewyang204/NotepadEE." 46 | 47 | uninstall-upgrade: 48 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 49 | sudo rm -f $(BIN_LINK) 50 | sudo rm -rf $(DESKTOP_FILE) 51 | 52 | uninstall-no-desktop-file: 53 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 54 | sudo rm -f $(BIN_LINK) 55 | echo "Uninstalled. If issues experienced caused you to uninstall Notepad==, please report them on https://www.github.com/matthewyang204/NotepadEE." 56 | 57 | uninstall-no-desktop-file-upgrade: 58 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 59 | sudo rm -f $(BIN_LINK) 60 | 61 | clean: clean-dist clean-bin-dist 62 | rm -rf dist 63 | rm -rf build 64 | rm -rf $(APP_NAME).spec 65 | echo "Cleaned all temp files used" 66 | 67 | dist: clean 68 | mkdir ../temp 69 | cp -R ./* ../temp/ 70 | tar -czvf NotepadEE-Linux-src.tar.gz -C ../temp . 71 | rm -rf ../temp 72 | 73 | clean-dist: 74 | rm -rf NotepadEE-Linux-src.tar.gz 75 | rm -rf ../temp 76 | 77 | bin-dist: 78 | mkdir ../temp 79 | cp -R dist build *.spec notepadee.desktop ../temp/ 80 | cp resources/dist.mak ../temp/Makefile 81 | tar -czvf NotepadEE-Linux-$(VERSION_NUMBER)-$(shell uname -m).tar.gz -C ../temp . 82 | rm -rf ../temp 83 | 84 | clean-bin-dist: 85 | rm -rf ../temp 86 | rm -rf NotepadEE-Linux-*.tar.gz 87 | 88 | upgrade: uninstall-upgrade install 89 | 90 | upgrade-no-desktop-file: uninstall-no-desktop-file-upgrade install-no-desktop-file 91 | -------------------------------------------------------------------------------- /Linux/Notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewyang204/NotepadEE/bbe680a2771952db48061e847de6ef5c3911b52d/Linux/Notepad.png -------------------------------------------------------------------------------- /Linux/README.md: -------------------------------------------------------------------------------- 1 | # Notepad== Linux Version 2 | 3 | This is the repository for the linux version of this software. 4 | 5 | Main repository: https://www.github.com/matthewyang204/NotepadEE 6 | 7 | This is the Linux version's source code 8 | 9 | The Linux binaries can be downloaded from the releases 10 | 11 | Any Debian-based distro works for building, otherwise, any distro can be used with the binaries 12 | 13 | Python 3.4 is minimum version that you can use to build 14 | 15 | Build instructions: 16 | - Please clone the repo and then cd into the Linux folder within the cloned repo in a terminal 17 | - If you haven't yet, please run the `dpinstall` script to install dependencies if you haven't yet 18 | - Type `./configure && make && sudo make install` to build from source and install 19 | - If the `./configure` script needs to install stuff, go ahead and enter your password if prompted 20 | - You can use `sudo make upgrade` instead of `sudo make install` to directly update your existing installation 21 | - Any GUI distro from the last 10-15 years should work for building from source and running binaries 22 | - Source code is in the Linux folder; macOS source code is in separate macOS folder 23 | -------------------------------------------------------------------------------- /Linux/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check for Python 3 4 | printf "Checking for python3..." 5 | if ! command -v python3 &> /dev/null 6 | then 7 | echo "not found" 8 | echo "Python 3 not found. Please install it to continue." 9 | exit 1 10 | else 11 | echo "found" 12 | fi 13 | 14 | # Check Python 3 version 15 | printf "Checking python3 version..." 16 | version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') 17 | echo $version 18 | 19 | # version=3.11.5 20 | # version=3.13.2 21 | # version=3.12 22 | # if [[ $version == 3.12.* ]]; then 23 | # echo "correct" 24 | # elif [[ $version == 3.13.* ]]; then 25 | # echo "correct" 26 | # else 27 | # echo "incorrect" 28 | # echo "The version of Python 3 currently installed is not a 3.12 release. Please install a 3.12 release to continue." 29 | # exit 1 30 | # fi 31 | 32 | # Set requirements file 33 | printf "Setting requirements.txt..." 34 | if [[ $version == 3.4.* ]]; then 35 | requirements=requirements-3.4.txt 36 | elif [[ $version == 3.5.x ]]; then 37 | requirements=requirements-3.4.txt 38 | else 39 | requirements=requirements.txt 40 | fi 41 | echo "done" 42 | 43 | # Check for pip3 44 | printf "Checking for pip3..." 45 | if ! command -v pip3 &> /dev/null 46 | then 47 | echo "not found" 48 | echo "pip3 was not found. Please install it to your respective Python 3 installation to continue." 49 | exit 1 50 | else 51 | echo "found" 52 | fi 53 | 54 | # Check if tkinter is available 55 | printf "Checking if python3 supports tkinter..." 56 | if ! command -v python3 -c 'import tkinter' &> /dev/null 57 | then 58 | echo "not available" 59 | echo "Tkinter not available. It is a core framework and required for compilation. Please compile python with tkinter or install a package containing tkinter to continue." 60 | exit 1 61 | else 62 | echo "available" 63 | fi 64 | 65 | # Check for idlelib 66 | printf "Checking if python3 supports idlelib..." 67 | if ! command -v python3 -c 'import idlelib' &> /dev/null 68 | then 69 | echo "not available" 70 | echo "IDLElib is not available. It is required for syntax highlighting. Please install it to continue." 71 | exit 1 72 | else 73 | echo "available" 74 | fi 75 | 76 | # Check for some dpkg and apt packages 77 | printf "Checking if patchelf is installed..." 78 | if ! command -v patchelf >/dev/null 2>&1; then 79 | echo "not installed" 80 | echo "patchelf is not installed. Please install it to continue." 81 | exit 1 82 | else 83 | echo "installed" 84 | fi 85 | 86 | # printf "Checking if python3-tk is installed..." 87 | # if ! dpkg -l | grep python3-tk >/dev/null 2>&1; then 88 | # echo "not installed" 89 | # echo "python3-tk is not installed. Please install it to continue." 90 | # exit 1 91 | # else 92 | # echo "installed" 93 | # fi 94 | 95 | printf "Checking if python3-dev is installed..." 96 | if ! dpkg -l | grep python3-dev >/dev/null 2>&1; then 97 | echo "not installed" 98 | echo "python3-dev is not installed. Please install it to continue." 99 | exit 1 100 | else 101 | echo "installed" 102 | fi 103 | 104 | # Check for tar 105 | printf "Checking for tar..." 106 | if ! command -v tar &> /dev/null 107 | then 108 | echo "not found" 109 | echo "Tar not found. Install it before continuing." 110 | exit 1 111 | else 112 | echo "found" 113 | fi 114 | 115 | # Install required python packages 116 | echo "Installing required packages" 117 | if python3 -m pip install -r $requirements --break-system-packages &> /dev/null; then 118 | echo "Required python packages installed" 119 | else 120 | echo "Command failed, probably because this is running on python that comes from apt on Debian-based distros, running without --break-system-packages" 121 | python3 -m pip install -r $requirements 122 | fi 123 | echo "done" 124 | 125 | touch .configured 126 | echo "Now run 'make' to build." 127 | -------------------------------------------------------------------------------- /Linux/dpinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This script needs to install some packages as root, so please enter your password if prompted..." 4 | echo "Installing system-level packages..." 5 | 6 | echo "Updating apt first..." 7 | yes | sudo apt-get update 8 | 9 | echo "Installing packages..." 10 | yes | sudo apt-get install python3 python3-tk python3-venv python3-dev python3-pip idle patchelf tar 11 | # if ! command -v yes | sudo apt-get install python3.12 python3.12-tk python3.12-dev patchelf tar 12 | # then 13 | # echo "done" 14 | # else 15 | # echo "Command failed, probably because Deadsnakes PPA is not installed. Please add the deadsnakes PPA to continue." 16 | # exit 1 17 | # fi 18 | 19 | # echo "Configuring symlinks..." 20 | # sudo ln -sf $(which python3.12) /usr/bin/python3 21 | # sudo ln -sf $(which python3.12) /usr/bin/python 22 | 23 | echo "Configuring pip..." 24 | sudo python3 -m ensurepip --upgrade 25 | 26 | echo "Installing python packages with pip..." 27 | if python3 -m pip install -r requirements.txt --break-system-packages 28 | then 29 | echo "Required python packages installed" 30 | else 31 | echo "Command failed, probably because this is running on python that comes from apt on Debian-based distros, running without --break-system-packages..." 32 | python3 -m pip install -r requirements.txt 33 | fi 34 | echo "done" 35 | -------------------------------------------------------------------------------- /Linux/notepadee.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Notepad== 3 | Exec=/opt/matthewyang/Notepad==/Notepad== %f 4 | Icon=/opt/matthewyang/Notepad==/Notepad.png 5 | Type=Application 6 | Terminal=false 7 | MimeType=text/plain; 8 | Categories=Application;Utility;TextEditor; 9 | Keywords=Text;Editor;Plaintext;Write; 10 | -------------------------------------------------------------------------------- /Linux/requirements-3.4.txt: -------------------------------------------------------------------------------- 1 | pefile==2019.4.18 2 | pyinstaller==3.3.1 3 | pillow 4 | tk 5 | -------------------------------------------------------------------------------- /Linux/requirements.txt: -------------------------------------------------------------------------------- 1 | pyinstaller 2 | pillow 3 | tk -------------------------------------------------------------------------------- /Linux/resources/dist.mak: -------------------------------------------------------------------------------- 1 | # Variables 2 | APP_NAME = Notepad== 3 | BUILD_DIR = dist 4 | INSTALL_DIR = /opt/matthewyang 5 | BIN_LINK = /usr/local/bin/notepadee 6 | DESKTOP_FILE = /usr/share/applications/notepadee.desktop 7 | 8 | .SILENT: 9 | 10 | install: 11 | sudo mkdir -p $(INSTALL_DIR) 12 | sudo cp -R $(BUILD_DIR)/$(APP_NAME) $(INSTALL_DIR)/ 13 | sudo ln -s $(INSTALL_DIR)/$(APP_NAME)/$(APP_NAME) $(BIN_LINK) 14 | sudo cp notepadee.desktop $(DESKTOP_FILE) 15 | sudo update-desktop-database 16 | echo "Install completed" 17 | 18 | install-no-desktop-file: 19 | sudo mkdir -p $(INSTALL_DIR) 20 | sudo cp -R $(BUILD_DIR)/$(APP_NAME) $(INSTALL_DIR)/ 21 | sudo ln -s $(INSTALL_DIR)/$(APP_NAME)/$(APP_NAME) $(BIN_LINK) 22 | echo "Install completed" 23 | 24 | uninstall: 25 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 26 | sudo rm -f $(BIN_LINK) 27 | sudo rm -rf $(DESKTOP_FILE) 28 | echo "Uninstalled. If issues experienced caused you to uninstall Notepad==, please report them on https://www.github.com/matthewyang204/NotepadEE." 29 | 30 | uninstall-upgrade: 31 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 32 | sudo rm -f $(BIN_LINK) 33 | sudo rm -rf $(DESKTOP_FILE) 34 | 35 | uninstall-no-desktop-file: 36 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 37 | sudo rm -f $(BIN_LINK) 38 | echo "Uninstalled. If issues experienced caused you to uninstall Notepad==, please report them on https://www.github.com/matthewyang204/NotepadEE." 39 | 40 | uninstall-no-desktop-file-upgrade: 41 | sudo rm -rf /opt/matthewyang/$(APP_NAME) 42 | sudo rm -f $(BIN_LINK) 43 | 44 | upgrade: uninstall-upgrade install 45 | 46 | upgrade-no-desktop-file: uninstall-no-desktop-file-upgrade install-no-desktop-file -------------------------------------------------------------------------------- /Linux/src/Notepad==.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import filedialog 3 | import os 4 | from tkinter import messagebox 5 | from tkinter import font 6 | import sys 7 | import time 8 | import platform 9 | import subprocess 10 | import threading 11 | # import atexit 12 | import signal 13 | import errno 14 | try: 15 | import idlelib.colorizer as ic 16 | import idlelib.percolator as ip 17 | syntaxHighlighting = True 18 | except ImportError: 19 | syntaxHighlighting = False 20 | import re 21 | import pathlib 22 | 23 | # Define and create, if applicable, a cache folder 24 | cache_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'cache') 25 | if not os.path.exists(cache_path): 26 | os.makedirs(cache_path) 27 | 28 | # Open a log file in write mode 29 | # log_file = os.path.join(cache_path, "notepadee_log.txt") 30 | log_file = os.path.join('/tmp', "notepadee_log.txt") 31 | 32 | # Get current PID 33 | pid = os.getpid() 34 | 35 | # Special printlog statement to print stuff that doesn't belong in a console to the log file 36 | def printlog(message): 37 | with open(log_file, 'a') as file: 38 | file.write("Notepad== at " + str(pid) + ": " + str(message)) 39 | print("Notepad== at " + str(pid) + ": " + str(message)) 40 | 41 | versionInfo = """Notepad==, version 5.1.0 42 | (C) 2024-2025 Matthew Yang""" 43 | 44 | helpInfo = versionInfo + """ 45 | 46 | Usage: notepadee [OPTIONS] [] 47 | 48 | Options: 49 | --version, -v Display version info and exit 50 | --help, -h Display this help message and exit 51 | 52 | Note that [] is not required and if not given, the file that was previously opened will be opened in the new instance. 53 | """ 54 | 55 | arg = sys.argv 56 | if len(arg) <= 1: 57 | pass 58 | else: 59 | if arg[1] == '--version' or arg[1] == '-v': 60 | print(versionInfo) 61 | sys.exit() 62 | elif arg[1] == '--help' or arg[1] == '-h': 63 | print(helpInfo) 64 | sys.exit() 65 | 66 | global fileToBeOpened 67 | global openFile 68 | fileToBeOpened = None 69 | openFile = None 70 | 71 | folder_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs') 72 | 73 | global file_open 74 | file_open = 0 75 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 76 | if os.path.exists(last_file_path): 77 | with open(last_file_path, 'r') as file: 78 | current_file = file.read() 79 | if current_file.strip() == '': # Check if the file is empty 80 | file_open = 0 81 | else: 82 | file_open = 1 83 | else: 84 | current_file = "" 85 | file_open = 0 86 | 87 | global last_write 88 | last_write = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write') 89 | 90 | file_written = 0 91 | printlog("file_written set to " + str(file_written)) 92 | 93 | def setup_prefs(event=None): 94 | global folder_path, last_file_path, last_write 95 | 96 | if not os.path.exists(folder_path): 97 | os.makedirs(folder_path) 98 | 99 | if not os.path.exists(last_file_path): 100 | with open(last_file_path, 'w'): 101 | pass 102 | 103 | if not os.path.exists(last_write): 104 | with open(last_write, 'w'): 105 | pass 106 | 107 | setup_prefs() 108 | 109 | class platformError(Exception): 110 | pass 111 | 112 | root = tk.Tk() 113 | ask_quit = False 114 | root.title("Notepad==") 115 | root.minsize(800, 600) 116 | root.pack_propagate(False) 117 | 118 | status_frame = tk.Frame(root) 119 | status_frame.pack() 120 | 121 | line_var = tk.StringVar() 122 | line_label = tk.Label(status_frame, textvariable=line_var) 123 | line_label.pack(side=tk.LEFT) 124 | 125 | column_var = tk.StringVar() 126 | column_label = tk.Label(status_frame, textvariable=column_var) 127 | column_label.pack(side=tk.LEFT) 128 | 129 | word_count_var = tk.StringVar() 130 | word_count_label = tk.Label(status_frame, textvariable=word_count_var) 131 | word_count_label.pack(side=tk.LEFT) 132 | 133 | text_size_indicator = tk.StringVar() 134 | size_label = tk.Label(status_frame, textvariable=text_size_indicator) 135 | size_label.pack(side=tk.LEFT) 136 | 137 | file_var = tk.StringVar() 138 | file_label = tk.Label(status_frame, textvariable=file_var) 139 | file_label.pack(side=tk.LEFT) 140 | 141 | def get_font_for_platform(): 142 | if os.name == 'nt': 143 | return font.Font(family="Consolas", size=12) 144 | elif os.uname().sysname == 'Darwin': 145 | return font.Font(family="Menlo", size=12) 146 | else: 147 | return font.Font(family="DejaVu Sans Mono", size=12) 148 | 149 | text_font = get_font_for_platform() 150 | text_area = tk.Text(root, width=100, height=80, wrap=tk.WORD, undo=True) 151 | text_area.config(font=text_font) 152 | 153 | if syntaxHighlighting: 154 | try: 155 | cdg = ic.ColorDelegator() 156 | cdg.prog = re.compile(r'\b(?Ptkinter)\b|' + ic.make_pat().pattern, re.S) 157 | cdg.idprog = re.compile(r'\s+(\w+)', re.S) 158 | 159 | cdg.tagdefs['MYGROUP'] = {'foreground': '#7F7F7F', 'background': ''} 160 | 161 | # For platforms with malfunctioning idlelibs, force the standard colors 162 | if platform.system() == "Darwin": 163 | cdg.tagdefs['COMMENT'] = {'foreground': '#dd0000', 'background': ''} # red 164 | cdg.tagdefs['KEYWORD'] = {'foreground': '#F2A061', 'background': ''} # orange 165 | cdg.tagdefs['BUILTIN'] = {'foreground': '#900090', 'background': ''} # purple 166 | cdg.tagdefs['STRING'] = {'foreground': '#00aa00', 'background': ''} # green 167 | cdg.tagdefs['DEFINITION'] = {'foreground': '#000000', 'background': ''} # black 168 | except AttributeError: 169 | cdg = ic.ColorDelegator() 170 | cdg.prog = re.compile(r'\b(?Ptkinter)\b|' + ic.make_pat(), re.S) 171 | cdg.idprog = re.compile(r'\s+(\w+)', re.S) 172 | 173 | cdg.tagdefs['MYGROUP'] = {'foreground': '#7F7F7F', 'background': ''} 174 | 175 | # For platforms with malfunctioning idlelibs, force the standard colors 176 | if platform.system() == "Darwin": 177 | cdg.tagdefs['COMMENT'] = {'foreground': '#dd0000', 'background': ''} # red 178 | cdg.tagdefs['KEYWORD'] = {'foreground': '#F2A061', 'background': ''} # orange 179 | cdg.tagdefs['BUILTIN'] = {'foreground': '#900090', 'background': ''} # purple 180 | cdg.tagdefs['STRING'] = {'foreground': '#00aa00', 'background': ''} # green 181 | cdg.tagdefs['DEFINITION'] = {'foreground': '#000000', 'background': ''} # black 182 | else: 183 | printlog("Platform does not support newer idlelibs, syntax highlighting is disabled") 184 | 185 | text_area.delete(1.0, "end") 186 | with open(last_write, 'r') as file: 187 | text_area.insert(1.0, file.read()) 188 | if platform.system() == "Darwin" or platform.system() == "Linux": 189 | printlog("Clearing any locks...") 190 | subprocess.call(["/bin/rm", "-rf", os.path.join(cache_path, "loadPreviousSave.lock")]) 191 | else: 192 | printlog("We are on a system that does not need or use file locks, skipping...") 193 | 194 | def runonarg(arg): 195 | global file_written, current_file, file_open 196 | if os.path.exists(arg): 197 | with open(arg, 'r') as file: 198 | if file_written == 1: 199 | if platform.system() == "Darwin": 200 | newWindow_macOS(openFile=arg) 201 | elif platform.system() == "Linux": 202 | newWindow_Linux(openFile=arg) 203 | else: 204 | text_area.delete(1.0, "end") 205 | current_file = arg 206 | text_area.insert(1.0, file.read()) 207 | file_open = 1 208 | else: 209 | text_area.delete(1.0, "end") 210 | current_file = arg 211 | text_area.insert(1.0, file.read()) 212 | file_open = 1 213 | #printlog("Current file path: " + current_file) 214 | #printlog("File open: " + str(file_open)) 215 | printlog("File loaded") 216 | else: 217 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), arg) 218 | 219 | # text_area.delete(1.0, "end") 220 | # with open(arg, 'w') as file: 221 | # text = text_area.get(1.0, "end-1c") 222 | # file.write(text) 223 | # file_open = 1 224 | # current_file = os.path.abspath(arg) 225 | # #printlog("Current file path: " + current_file) 226 | # #printlog("File open: " + str(file_open)) 227 | # printlog("Because the file doesn't exist, it was created as a blank new file instead") 228 | 229 | # except Exception as e: 230 | # printlog("Exception " + str(e) + " caught!") 231 | 232 | # Check if the system is macOS (Darwin) 233 | if platform.system() == "Darwin": 234 | try: 235 | def addOpenEventSupport(root): 236 | """ 237 | Enable the application to handle macOS 'Open with' events. 238 | """ 239 | fileToBeOpenedPath = os.path.join(cache_path, "fileToBeOpened.txt") 240 | openFilePath = os.path.join(cache_path, "openFile.txt") 241 | 242 | def doOpenFile(*args): 243 | global fileToBeOpened, openFile 244 | if args: 245 | fileToBeOpened = str(args[0]) 246 | openFile = 1 247 | printlog("File was passed from Finder, loading file...") 248 | runonarg(fileToBeOpened) 249 | 250 | else: 251 | fileToBeOpened = "" 252 | openFile = 0 253 | printlog("No file passed from Finder, loading program with last known file...") 254 | printlog("Program loaded") 255 | 256 | printlog("fileToBeOpened: " + str(fileToBeOpened)) 257 | printlog("openFile: " + str(openFile)) 258 | # Hook into macOS-specific file open event 259 | root.createcommand("::tk::mac::OpenDocument", doOpenFile) 260 | 261 | addOpenEventSupport(root) 262 | 263 | except Exception as e: 264 | fileToBeOpened = "" 265 | openFile = 0 266 | printlog(str(e)) 267 | printlog("fileToBeOpened: " + str(fileToBeOpened)) 268 | 269 | else: 270 | # Tell the user through the console that we are running on Linux 271 | printlog("We are running on a standard Linux distro or other OS, falling back to file arguments...") 272 | # If not macOS, fallback to command line arguments 273 | filearg = sys.argv 274 | if len(filearg) <= 1: 275 | openFile = 0 276 | printlog("No arguments provided. Proceeding to load program with last known file...") 277 | printlog("Program loaded") 278 | else: 279 | openFile = 1 280 | printlog("Assuming argument is the file to open. Loading file...") 281 | fileToBeOpened = filearg[1] 282 | runonarg(fileToBeOpened) 283 | 284 | 285 | def debug_var(event=None): 286 | global file_open, current_file 287 | if current_file: 288 | printlog("Current file variable works") 289 | printlog(current_file) 290 | else: 291 | printlog("Not intact") 292 | if file_open: 293 | printlog("File_open variable is intact") 294 | printlog(file_open) 295 | else: 296 | printlog("Not working") 297 | return 'break' 298 | 299 | 300 | def autosave_file(event=None): 301 | global current_file 302 | global file_open 303 | try: 304 | if file_open == 1: 305 | with open(current_file, 'w') as file: 306 | text = text_area.get('1.0', 'end-1c') 307 | file.write(text) 308 | except FileNotFoundError: 309 | return 'break' 310 | printlog("Autosaved file") 311 | 312 | 313 | def write_prefs(event=None): 314 | setup_prefs() 315 | 316 | global current_file, file_open 317 | with open(os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write'), 'w') as file: 318 | file.write(text_area.get('1.0', 'end-1c')) 319 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 320 | with open(last_file_path, 'w') as file: 321 | file.write(str(current_file)) 322 | autosave_file() 323 | printlog("Wrote prefs successfully") 324 | 325 | def spoof_prefs(current_file="", file_open=""): 326 | with open(os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write'), 'w') as file: 327 | file.write(text_area.get('1.0', 'end-1c')) 328 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 329 | with open(last_file_path, 'w') as file: 330 | file.write(str(current_file)) 331 | autosave_file() 332 | printlog("Wrote prefs successfully") 333 | 334 | # save_as provides the dialog 335 | def save_as(event=None): 336 | global current_file, file_open 337 | file_path = filedialog.asksaveasfilename( 338 | defaultextension="", 339 | filetypes=( 340 | ("All Files", "*.*"), 341 | 342 | # Plain text files 343 | ("Plain text file", ".txt"), 344 | ("Log file", ".log"), 345 | 346 | # ms ini/inf 347 | ("INI file", ".ini"), 348 | ("INF file (.inf)", ".inf"), 349 | 350 | # C, C++, objc 351 | ("C, C++, objc header", ".h"), 352 | ("C, C++, objc header", ".hh"), 353 | ("C, C++, objc header", ".hpp"), 354 | ("C, C++, objc header", ".hxx"), 355 | ("C, C++, objc source", ".c"), 356 | ("C, C++, objc source", ".cpp"), 357 | ("C, C++, objc source", ".cxx"), 358 | ("C, C++, objc source", ".cc"), 359 | ("C, C++, objc source", ".m"), 360 | ("C, C++, objc source", ".mm"), 361 | ("C, C++, objc project", ".vcxproj"), 362 | ("C, C++, objc project", ".vcproj"), 363 | ("C, C++, objc properties", ".props"), 364 | ("C, C++, objc properties", ".vsprops"), 365 | ("C, C++, objc manifest", ".manifest"), 366 | 367 | # Java, C#, Pascal 368 | ("Java file", ".java"), 369 | ("Pascal file", ".pas"), 370 | ("Pascal file", ".pp"), 371 | ("Include file", ".inc"), 372 | 373 | # .NET code 374 | ("Visual Basic (.vb)", ".vb"), 375 | ("Visual Basic script (.vbs)", ".vbs"), 376 | ("C# file (.cs)", ".cs"), 377 | 378 | # Web script files 379 | ("HTML file", ".html"), 380 | ("HTML file", ".htm"), 381 | ("Server-side HTML", ".shtml"), 382 | ("Server-side HTML", ".shtm"), 383 | ("HTML Application", ".hta"), 384 | ("ASP file", ".asp"), 385 | ("ASP.NET file", ".aspx"), 386 | ("CSS file", ".css"), 387 | ("JavaScript file", ".js"), 388 | ("JSON file", ".json"), 389 | ("JavaScript module", ".mjs"), 390 | ("JavaScript module", ".jsm"), 391 | ("JSP file", ".jsp"), 392 | ("PHP file", ".php"), 393 | ("PHP3 file", ".php3"), 394 | ("PHP4 file", ".php4"), 395 | ("PHP5 file", ".php5"), 396 | ("PHP script", ".phps"), 397 | ("PHP script", ".phpt"), 398 | ("PHP file", ".phtml"), 399 | ("XML file", ".xml"), 400 | ("XHTML file", ".xhtml"), 401 | ("XHTML file", ".xht"), 402 | ("XUL file", ".xul"), 403 | ("KML file", ".kml"), 404 | ("XAML file", ".xaml"), 405 | ("XSML file", ".xsml"), 406 | 407 | # Script files 408 | ("Shell script", ".sh"), 409 | ("Bash script", ".bsh"), 410 | ("Bash script", ".bash"), 411 | ("Batch file", ".bat"), 412 | ("Command file", ".cmd"), 413 | ("NSIS script", ".nsi"), 414 | ("NSIS header", ".nsh"), 415 | ("Lua script", ".lua"), 416 | ("Perl script", ".pl"), 417 | ("Perl module", ".pm"), 418 | ("Python script", ".py"), 419 | ("Inno Setup script", ".iss"), 420 | ("Makefile", ".mak"), 421 | 422 | # Property scripts 423 | ("Resource file", ".rc"), 424 | ("ActionScript", ".as"), 425 | ("MaxScript", ".mx"), 426 | 427 | # Fortran, TeX, SQL 428 | ("Fortran file", ".f"), 429 | ("Fortran file", ".for"), 430 | ("Fortran 90 file", ".f90"), 431 | ("Fortran 95 file", ".f95"), 432 | ("Fortran 2000 file", ".f2k"), 433 | ("TeX file", ".tex"), 434 | ("SQL file", ".sql"), 435 | 436 | # Miscellaneous files 437 | ("NFO file", ".nfo"))) 438 | current_file = file_path 439 | # if file_path doesn't exist, let's stop the function and return False 440 | if not file_path: 441 | return False 442 | 443 | # if we get a valid file_path, let's save via dialog 444 | try: 445 | printlog("Saving file to location:") 446 | printlog(file_path) 447 | with open(file_path, 'w') as file: 448 | text = text_area.get(1.0, "end-1c") 449 | file.write(text) 450 | write_prefs() 451 | file_open = 1 452 | printlog("File was saved to different location successfully.") 453 | return True 454 | 455 | # if any errors manage to get past this, let's do an exception to quit gracefully 456 | except FileNotFoundError: 457 | messagebox.showerror("Error", "Location nonexistent") 458 | return False 459 | 460 | def open_file(event=None): 461 | global current_file, file_open 462 | save_file("y") 463 | file_path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")]) 464 | if file_path: 465 | text_area.delete(1.0, "end") 466 | current_file = file_path 467 | with open(file_path, 'r') as file: 468 | text_area.insert(1.0, file.read()) 469 | file_open = 1 470 | printlog("New file opened") 471 | write_prefs() 472 | 473 | def open_file_v2(event=None): 474 | global current_file, file_written, file_open 475 | save_file("y") 476 | file_path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")]) 477 | if file_path: 478 | with open(file_path, 'r') as file: 479 | if file_written == 1: 480 | if platform.system() == "Darwin": 481 | newWindow_macOS(openFile=file_path) 482 | elif platform.system() == "Linux": 483 | newWindow_Linux(openFile=file_path) 484 | else: 485 | text_area.delete(1.0, "end") 486 | current_file = file_path 487 | text_area.insert(1.0, file.read()) 488 | file_open = 1 489 | else: 490 | text_area.delete(1.0, "end") 491 | current_file = file_path 492 | text_area.insert(1.0, file.read()) 493 | file_open = 1 494 | printlog("New file opened") 495 | # write_prefs() 496 | 497 | def save_file(warn): 498 | global current_file, file_open, file_written 499 | if file_open == 1: 500 | try: 501 | debug_var() 502 | with open(current_file, 'w') as file: 503 | text = text_area.get('1.0', 'end-1c') 504 | file.write(text) 505 | write_prefs() 506 | return True 507 | except FileNotFoundError: 508 | return 'break' 509 | else: 510 | if warn == "y": 511 | response = messagebox.askyesno("Warning: File is not saved","The current file is not saved. Do you want to save it to a selected location?") 512 | if response: 513 | if save_as(): 514 | printlog("File saved without warning") 515 | return True 516 | else: 517 | return True 518 | elif warn == "w": 519 | if file_written == 1: 520 | response = messagebox.askyesnocancel("Warning: File is not saved","The current file is not saved. Changes may be lost if they are not saved. Do you want to save before exiting?") 521 | if response: 522 | if save_as(): 523 | printlog("File saved") 524 | return True 525 | elif response == None: 526 | return False 527 | else: 528 | return True 529 | else: 530 | return True 531 | else: 532 | response = messagebox.askyesno("Create new file","The file does not exist. Do you want to create it as a new file before proceeding?") 533 | if response: 534 | if save_as(): 535 | printlog("File saved after warning user") 536 | return True 537 | else: 538 | return True 539 | 540 | def save_file2(event=None): 541 | global current_file, file_open, file_written 542 | printlog("No-warning wrapper triggered, running save_file with nowarning option") 543 | save_file("n") 544 | 545 | def new_file(event=None): 546 | global current_file, file_open, file_written 547 | 548 | # Check if there is text in text_area 549 | if file_written == 1: 550 | # Only run this code if save_file, otherwise, don't force user to clear 551 | if save_file("y"): 552 | text_area.delete(1.0, "end") 553 | printlog("Cleared text_area") 554 | current_file = "" 555 | write_prefs() 556 | file_open = 0 557 | printlog("New file created") 558 | file_written = 0 559 | 560 | # Otherwise, clear without obstruction 561 | else: 562 | text_area.delete(1.0, "end") 563 | printlog("Cleared text_area") 564 | current_file = "" 565 | 566 | 567 | def cut_text(event=None): 568 | text_area.clipboard_clear() 569 | text_area.clipboard_append(text_area.get("sel.first", "sel.last")) 570 | text_area.delete("sel.first", "sel.last") 571 | printlog("Cut option succeeded") 572 | return 'break' 573 | 574 | 575 | def copy_text(event=None): 576 | text_area.clipboard_clear() 577 | text_area.clipboard_append(text_area.get("sel.first", "sel.last")) 578 | printlog("Text copied to clipboard") 579 | return 'break' 580 | 581 | 582 | def paste_text(event=None): 583 | text_area.insert("insert", text_area.clipboard_get()) 584 | printlog("Text pasted from clipboard") 585 | return 'break' 586 | 587 | 588 | def select_all_text(event=None): 589 | text_area.tag_add("sel", "1.0", "end") 590 | printlog("Text selected") 591 | return 'break' 592 | 593 | 594 | def undo(event=None): 595 | try: 596 | text_area.edit_undo() 597 | except tk.TclError: 598 | pass 599 | printlog("Edit undone") 600 | 601 | 602 | def redo(event=None): 603 | try: 604 | text_area.edit_redo() 605 | except tk.TclError: 606 | pass 607 | printlog("Edit redone") 608 | 609 | 610 | def find_and_replace(event=None): 611 | popup = tk.Toplevel(root) 612 | popup.title("Find and Replace") 613 | 614 | find_label = tk.Label(popup, text="Enter the text you want to find:") 615 | find_label.pack() 616 | find_entry = tk.Entry(popup) 617 | find_entry.pack() 618 | 619 | replace_label = tk.Label( 620 | popup, text="Enter the text you want to replace it with:") 621 | replace_label.pack() 622 | replace_entry = tk.Entry(popup) 623 | replace_entry.pack() 624 | 625 | def perform_replace(event=None): 626 | find_text = find_entry.get() 627 | replace_text = replace_entry.get() 628 | 629 | text_widget = text_area.get("1.0", tk.END) 630 | if find_text: 631 | text_widget = text_widget.replace(find_text, replace_text) 632 | text_area.delete("1.0", tk.END) 633 | text_area.insert(tk.END, text_widget) 634 | 635 | def close(event=None): 636 | popup.destroy() 637 | 638 | replace_button = tk.Button(popup, text="Replace", command=perform_replace) 639 | close_button = tk.Button(popup, text="Close", command=close) 640 | replace_button.pack() 641 | close_button.pack() 642 | find_entry.bind('', perform_replace) 643 | replace_entry.bind('', perform_replace) 644 | 645 | def go_to_line(event=None): 646 | popup = tk.Toplevel(root) 647 | popup.title("Go To Line") 648 | 649 | line_number_label = tk.Label(popup, text="Enter the line that you want to go to:") 650 | line_number_label.pack() 651 | entrybox = tk.Entry(popup) 652 | entrybox.pack() 653 | 654 | def go(event=None): 655 | line_number = entrybox.get() 656 | text_area.mark_set("insert", str(line_number) + ".0") 657 | 658 | def close(event=None): 659 | popup.destroy() 660 | 661 | go_to_line_button = tk.Button(popup, text="Go", command=go) 662 | close_button = tk.Button(popup, text="Close", command=close) 663 | go_to_line_button.pack() 664 | close_button.pack() 665 | entrybox.bind('', go) 666 | 667 | def cPos(index): 668 | line, column = text_area.index("insert").split(".") 669 | 670 | if index == "both": 671 | return line, column 672 | elif index == "line": 673 | return line 674 | elif index == "column": 675 | return column 676 | else: 677 | printlog("invalidArg") 678 | return "invalidArg" 679 | 680 | def findNext(text): 681 | try: 682 | last_highlight = text_area.index("highlight.last") 683 | start = last_highlight 684 | except tk.TclError: 685 | cPos_line, cpos_column = cPos("both") 686 | start = str(cPos_line) + "." + str(cPos_column) 687 | # start= "1.0" 688 | 689 | text_area.tag_remove("highlight", "1.0", "end") 690 | try: 691 | start = text_area.search(text, start, stopindex="end") 692 | end = str(start) + " + " + str(len(text)) + "c" 693 | text_area.tag_add("highlight", start, end) 694 | except Exception as e: 695 | start = "1.0" 696 | start = text_area.search(text, start, stopindex="end") 697 | end = str(start) + " + " + str(len(text)) + "c" 698 | text_area.tag_add("highlight", start, end) 699 | 700 | text_area.tag_config("highlight", background="yellow") 701 | 702 | def find_text(event=None): 703 | popup = tk.Toplevel(root) 704 | popup.title("Find") 705 | 706 | line_number_label = tk.Label(popup, text="Enter the text that you want to find:") 707 | line_number_label.pack() 708 | entrybox = tk.Entry(popup) 709 | entrybox.pack() 710 | 711 | def findNext_wrapper(event=None): 712 | findNext(entrybox.get()) 713 | 714 | def clear(event=None): 715 | text_area.tag_remove("highlight", "1.0", "end") 716 | 717 | def close(event=None): 718 | clear() 719 | popup.destroy() 720 | 721 | find_button = tk.Button(popup, text="Find Next", command = findNext_wrapper) 722 | close_button = tk.Button(popup, text="Close", command=close) 723 | clear_button = tk.Button(popup, text="Clear", command=clear) 724 | find_button.pack() 725 | clear_button.pack() 726 | close_button.pack() 727 | entrybox.bind('', findNext_wrapper) 728 | 729 | def mark_text(event=None): 730 | selectStart = text_area.index("sel.first") 731 | selectEnd = text_area.index("sel.last") 732 | # DO NOT enable this 733 | # printlog(f"Current selection is {selectStart}, {selectEnd}") 734 | printlog("Clearing all current highlights in selection...") 735 | text_area.tag_remove("highlight_permanent", selectStart, selectEnd) 736 | printlog("Configuring highlight_permanent tags to selection...") 737 | text_area.tag_add("highlight_permanent", selectStart, selectEnd) 738 | printlog("Configuring tagged text to highlight...") 739 | text_area.tag_config("highlight_permanent", background="green") 740 | printlog("done") 741 | 742 | def unmark_text(event=None): 743 | selectStart = text_area.index("sel.first") 744 | selectEnd = text_area.index("sel.last") 745 | # DO NOT enable this 746 | # printlog(f"Current selection is {selectStart}, {selectEnd}") 747 | printlog("Clearing all current highlights in selection...") 748 | text_area.tag_remove("highlight_permanent", selectStart, selectEnd) 749 | printlog("done") 750 | 751 | def unmark_all_text(event=None): 752 | printlog("Clearing all current highlights...") 753 | text_area.tag_remove("highlight_permanent", "1.0", "end") 754 | printlog("done") 755 | 756 | def update_line_number(event=None): 757 | line, column = text_area.index(tk.INSERT).split('.') 758 | line_var.set("Line: " + line) 759 | column_var.set("Column: " + column) 760 | words = text_area.get(1.0, 'end-1c').split() 761 | word_count_var.set("Words: " + str(len(words))) 762 | file_var.set("File: " + os.path.basename(current_file)) 763 | if current_file: 764 | root.title(str(current_file) + " - Notepad==") # f"{current_file} - Notepad==" 765 | else: 766 | root.title("Notepad==") 767 | text_size = text_font['size'] 768 | text_size_indicator.set("Size: " + str(text_size)) # f"Size: {text_size}" 769 | # print("Status bar updated") 770 | root.after(100, update_line_number) 771 | 772 | def applySyntaxHighlighting(event=None): 773 | global current_file, syntaxHighlighting 774 | pythonExts = ['.py', '.pyw', '.pyc', '.pyo', '.pyd', '.pyx', '.pxd', '.pxi', '.pyi', '.ipynb', '.pyz'] 775 | if syntaxHighlighting: 776 | try: 777 | if pathlib.Path(os.path.basename(current_file)).suffix in pythonExts: 778 | ip.Percolator(text_area).insertfilter(cdg) 779 | else: 780 | if getattr(cdg, 'delegate', None) is not None: 781 | ip.Percolator(text_area).removefilter(cdg) 782 | except Exception as e: 783 | if getattr(cdg, 'delegate', None) is not None: 784 | ip.Percolator(text_area).removefilter(cdg) 785 | 786 | else: 787 | printlog("Python version does not support syntax highlighting") 788 | 789 | def increase_font_size(event=None): 790 | current_size = text_font['size'] 791 | text_font.config(size=current_size + 1) 792 | printlog("Font size increased by 1 pt") 793 | 794 | def decrease_font_size(event=None): 795 | current_size = text_font['size'] 796 | text_font.config(size=current_size - 1) 797 | printlog("Font size decreased by 1 pt") 798 | 799 | # Create a function to check for text in text_area 800 | def check_file_written(event=None): 801 | global file_written 802 | printlog("Checking if text_area has been edited by the user to contain text...") 803 | current_text = text_area.get(1.0, "end-1c") 804 | # if there is text, set it to 1 805 | if current_text: 806 | printlog("There is text; setting to 1") 807 | file_written = 1 808 | # otherwise, set it to 0 809 | else: 810 | printlog("No text") 811 | file_written = 0 812 | 813 | def runinbackground(event=None): 814 | write_prefs() 815 | check_file_written() 816 | applySyntaxHighlighting() 817 | debug_var() 818 | 819 | def newWindow_macOS(openFile=""): 820 | global folder_path 821 | if platform.system() == "Darwin": 822 | run_path = os.path.realpath(__file__) 823 | cwd = os.getcwd() 824 | freeze_time = 1 825 | emptyString = "" 826 | # printlog(f"Script path is {run_path}") 827 | # printlog(f"Current working directory is {cwd}") 828 | # printlog(f"App is located at {cwd}/Notepad==.app") 829 | # DO NOT enable this 830 | # printlog(f"Creating a lock file at {os.path.join(cache_path, "loadPreviousSave.lock")}...") 831 | with open(os.path.join(cache_path, "loadPreviousSave.lock"), "w") as file: 832 | file.write(emptyString) 833 | # DO NOT enable this 834 | # printlog(f"Clearing the prefs folder at {folder_path} to ensure new instance loads up with new file...") 835 | subprocess.call(["/bin/rm", "-rf", folder_path]) 836 | printlog("Launching new instance...") 837 | if openFile: 838 | subprocess.call(["/usr/bin/open", "-n", "-a", cwd + "/Notepad==.app", openFile]) 839 | else: 840 | subprocess.call(["/usr/bin/open", "-n", "-a", cwd + "/Notepad==.app"]) 841 | # DO NOT enable this 842 | # printlog(f"Waiting for {os.path.join(cache_path, "loadPreviousSave.lock")}...") 843 | while os.path.exists(os.path.join(cache_path, "loadPreviousSave.lock")): 844 | pass 845 | # DO NOT enable this 846 | # printlog(f"Writing cache back to prefs folder at {folder_path}...") 847 | write_prefs() 848 | printlog("done") 849 | else: 850 | raise platformError("This function is only designed to be run on macOS. We do not understand why you would want this function to run anyway, nor how you got it to run. The function needs to be specific to the platform.") 851 | 852 | def newWindow_Linux(openFile=""): 853 | def main(event=None): 854 | global folder_path 855 | run_path = os.path.realpath(__file__) 856 | cwd = os.getcwd() 857 | pyexe = sys.executable 858 | pyexe_dir = os.path.dirname(pyexe) 859 | pyInstFile = os.path.join(pyexe_dir, '.pyinstaller') 860 | freeze_time = 1 861 | 862 | # DO NOT enable 863 | # printlog(f"Script path is {run_path}") 864 | # printlog(f"Current working directory is {cwd}") 865 | # printlog(f"Executable is located at {pyexe}") 866 | emptyString = "" 867 | 868 | # DO NOT enable, this is only compatible with Python 3.12 and later 869 | # printlog(f"Creating a lock file at {os.path.join(cache_path, "loadPreviousSave.lock")}...") 870 | with open(os.path.join(cache_path, "loadPreviousSave.lock"), "w") as file: 871 | file.write(emptyString) 872 | # DO NOT enable 873 | # printlog(f"Clearing the prefs folder at {folder_path} to ensure new instance loads up with new file...") 874 | subprocess.call(["/bin/rm", "-rf", folder_path]) 875 | printlog("Launching new instance...") 876 | # Regular launcher with no open file support 877 | def launcher(): 878 | if os.path.exists(pyInstFile): 879 | printlog("We are running in PyInstaller mode, running only the executable...") 880 | subprocess.Popen([pyexe], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 881 | else: 882 | printlog("We are probably running in standard interpreted mode, launching executable with python file...") 883 | subprocess.Popen([pyexe, run_path], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 884 | # launcher with open file support 885 | def launcher2(): 886 | if os.path.exists(pyInstFile): 887 | printlog("We are running in PyInstaller mode, running only the executable...") 888 | subprocess.Popen([pyexe, openFile], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 889 | else: 890 | printlog("We are probably running in standard interpreted mode, launching executable with python file...") 891 | subprocess.Popen([pyexe, run_path, openFile], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 892 | if openFile: 893 | launcher_thread = threading.Thread(target=launcher2) 894 | else: 895 | launcher_thread = threading.Thread(target=launcher) 896 | launcher_thread.start() 897 | # DO NOT enable, this is only compatible with Python 3.12 and later 898 | # printlog(f"Waiting for {os.path.join(cache_path, "loadPreviousSave.lock")}...") 899 | while os.path.exists(os.path.join(cache_path, "loadPreviousSave.lock")): 900 | pass 901 | # DO NOT enable 902 | # printlog(f"Writing cache back to prefs folder at {folder_path}...") 903 | write_prefs() 904 | printlog("done") 905 | if platform.system() == "Linux": 906 | main() 907 | else: 908 | raise platformError("This function is only designed to be run on Linux. We do not understand why you would want this function to run anyway, nor how you got it to run. The function needs to be specific to the platform.") 909 | 910 | def newWindow(event=None): 911 | if platform.system() == "Darwin": 912 | newWindow_macOS() 913 | elif platform.system() == "Linux": 914 | newWindow_Linux() 915 | else: 916 | raise platformError("There is no newWindow function available for your platform.") 917 | 918 | def exit_handler(event=None): 919 | printlog("Telling user to save file before exit...") 920 | if save_file("w"): 921 | printlog("Exiting...") 922 | sys.exit() 923 | else: 924 | printlog("User pressed cancel, not exiting...") 925 | 926 | def exit_on_keyboardInterrupt(signum, frame): 927 | printlog("Received KeyboardInterrupt, running exit handler...") 928 | exit_handler() 929 | 930 | text_area.pack(fill=tk.BOTH, expand=tk.YES) 931 | text_area.bind('', runinbackground) 932 | text_area.bind('', runinbackground) 933 | runinbackground() 934 | update_line_number() 935 | 936 | menu = tk.Menu(root) 937 | root.config(menu=menu) 938 | 939 | file_menu = tk.Menu(menu) 940 | menu.add_cascade(label="File", menu=file_menu) 941 | # file_menu.add_command(label="New", command=new_file) 942 | # if platform.system() == "Darwin": 943 | # file_menu.add_command(label="New Window", command=newWindow_macOS) 944 | # elif platform.system() == "Linux": 945 | # file_menu.add_command(label="New Window", command=newWindow_Linux) 946 | file_menu.add_command(label="New", command=newWindow) 947 | file_menu.add_command(label="Open...", command=open_file_v2) 948 | file_menu.add_command(label="Save", command=save_file2) 949 | file_menu.add_command(label="Save as...", command=save_as) 950 | if platform.system() == "Linux": 951 | file_menu.add_command(label="Quit", command=exit_handler) 952 | 953 | edit_menu = tk.Menu(menu) 954 | menu.add_cascade(label="Edit", menu=edit_menu) 955 | edit_menu.add_command(label="Cut", command=cut_text) 956 | edit_menu.add_command(label="Copy", command=copy_text) 957 | edit_menu.add_command(label="Paste", command=paste_text) 958 | edit_menu.add_command(label="Select All", command=select_all_text) 959 | edit_menu.add_separator() 960 | edit_menu.add_command(label="Undo", command=undo) 961 | edit_menu.add_command(label="Redo", command=redo) 962 | edit_menu.add_separator() 963 | edit_menu.add_command(label="Mark Text", command=mark_text) 964 | edit_menu.add_command(label="Unmark Text", command=unmark_text) 965 | edit_menu.add_command(label="Unmark All Text", command=unmark_all_text) 966 | edit_menu.add_separator() 967 | edit_menu.add_command(label="Find", command=find_text) 968 | edit_menu.add_command(label="Find and Replace", command=find_and_replace) 969 | edit_menu.add_separator() 970 | edit_menu.add_command(label="Go To Line", command=go_to_line) 971 | 972 | accessibility_menu = tk.Menu(menu) 973 | menu.add_cascade(label="Accessibility", menu=accessibility_menu) 974 | accessibility_menu.add_command(label="Zoom in", command=increase_font_size) 975 | accessibility_menu.add_command(label="Zoom out", command=decrease_font_size) 976 | 977 | window_menu = tk.Menu(menu) 978 | menu.add_cascade(label="Window", menu=window_menu) 979 | window_menu.add_command(label="Close", command=exit_handler) 980 | 981 | root.bind_all("", exit_handler) 982 | 983 | if platform.system() == "Darwin": 984 | root.bind_all('', newWindow) 985 | root.bind_all('', newWindow) 986 | elif platform.system() == "Linux": 987 | root.bind_all('', newWindow) 988 | root.bind_all('', newWindow) 989 | root.bind_all('', open_file_v2) 990 | root.bind_all('', save_file) 991 | root.bind_all('', save_as) 992 | 993 | text_area.bind('', cut_text) 994 | text_area.bind('', copy_text) 995 | text_area.bind('', paste_text) 996 | text_area.bind('', select_all_text) 997 | text_area.bind('', undo) 998 | if platform.system() == "Darwin": 999 | text_area.bind('', redo) 1000 | else: 1001 | text_area.bind('', redo) 1002 | text_area.bind('', mark_text) 1003 | text_area.bind('', unmark_text) 1004 | text_area.bind('', unmark_all_text) 1005 | text_area.bind('', find_text) 1006 | text_area.bind('', find_and_replace) 1007 | text_area.bind('', go_to_line) 1008 | 1009 | text_area.bind('', increase_font_size) 1010 | text_area.bind('', decrease_font_size) 1011 | 1012 | # atexit.register(exit_handler) 1013 | signal.signal(signal.SIGINT, exit_on_keyboardInterrupt) 1014 | signal.signal(signal.SIGTERM, exit_on_keyboardInterrupt) 1015 | root.protocol('WM_DELETE_WINDOW', exit_handler) 1016 | 1017 | # Implement quit event if macOS 1018 | if platform.system() == "Darwin": 1019 | root.createcommand('::tk::mac::Quit', exit_handler) 1020 | elif platform.system() == "Linux": 1021 | root.bind_all("", exit_handler) 1022 | 1023 | write_prefs() 1024 | root.mainloop() 1025 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notepad== 2 | Have you ever wanted to jot something down on a Unix system and wished the Windows Notepad were there? Or have you ever wanted autosave and line numbering on your Windows 10 system? Then you can use Notepad==! Notepad== is a quick and simple text editor for POSIX systems. On Windows, this is a better notepad that has more features than the built-in one. Notepad== has autosave and line numbering, making it easier to take notes. Additionally, it doesn't carry Microsoft's heavy AI and UWP bloat. 3 | 4 | ----- 5 | Linux 6 | ----- 7 | - Download latest dev source code from the Linux section of this repository: https://github.com/matthewyang204/NotepadEE/tree/main/Linux 8 | - These binaries are next to the Windows binaries 9 | - Any Debian-based distro should work for building 10 | - Any GUI distro from the last 10-15 years should work for running binaries 11 | - You need Python 3.4 or later 12 | 13 | Linux build instructions: 14 | - Please clone the repo and cd into it 15 | - If you haven't yet, please run the `dpinstall` script to install dependencies to your system 16 | - Type `./configure && make && sudo make install` to build from source and install 17 | - If the `./configure` script needs to install stuff, enter your password if prompted 18 | - You can use `sudo make upgrade` instead of `sudo make install` to directly update your existing installation 19 | - Requires Debian-based distro to build 20 | - Source code is in the Linux folder; Windows source code is in separate Windows folder 21 | 22 | ----- 23 | Windows 24 | ----- 25 | - Download the latest dev source code from the Windows section of this repository: https://github.com/matthewyang204/NotepadEE/tree/main/Windows 26 | - Binaries are next to all other binaries 27 | - x64 binaries are provided for users, however, they are not signed 28 | - Version compatibility: 29 | - Windows XP or later; ARM64 systems need Win10 ARM64 or later 30 | - Python 3.4 or later 31 | 32 | Upgrading: 33 | - You can directly run the new installer to upgrade. You can either download this from the Release of the new version or you can install from your custom-built installer. 34 | 35 | ----- 36 | macOS 37 | ----- 38 | Source code located in the [macOS section](https://github.com/matthewyang204/NotepadEE/tree/main/macOS). 39 | 40 | Any Mac capable of running Python 3.4 works for building. Therefore, your Mac must be capable of running OS X 10.5 Leopard or later, as it is the earliest version of macOS supported by Python 3.4. 41 | 42 | Hackintoshes are supported. 43 | 44 | I have separate binaries for Intel and Apple Silicon macs. Please download the correct one. I have signed it with a self-signed signature and can't afford the full Apple Developer notarization. On macOS Sonoma or below, you can bypass the warnings by right-clicking the app and selecting "Open". On macOS Sequioa or later, you will need to disable Gatekeeper entirely by running `sudo spctl --master-disable` and then selecting "Anywhere" at the bottom of the Privacy & Security section of the settings in the "Allow apps from" setting. Alternatively, you may want to use homebrew, which is capable of bypassing Gatekeeper on installation with a single flag. 45 | 46 | To use it, first, tap my homebrew repo by running `brew tap matthewyang204/homebrew-formulae-casks`. After this, you can install the cask with `brew install --cask --no-quarantine notepadee`. 47 | 48 | All of the prebuilt binaries provided in the Releases support macOS Catalina or newer. 49 | 50 | Signing info for prebuilt binaries: 51 | - Self-signed signature 52 | - Name of signature is "Matthew Yang" 53 | 54 | Prebuild requirements: 55 | - Python 3.4 - 3.12 56 | - `make` installed 57 | - `gettext` and `tcl-tk` installed with Homebrew 58 | 59 | Build instructions: 60 | 1. Clone the repository and navigate to the macOS folder inside of it 61 | 2. Run `chmod +x autogen.sh` to give the script execute permissions. 62 | 3. Run `./autogen.sh --arch=` to automatically configure and build. Note that --arch is optional and if you only want to compile for your machine's native architecture, just run `./autogen.sh`. 63 | 4. After you're done compiling, you can use `sudo make install` to install. 64 | -------------------------------------------------------------------------------- /Windows/Notepad.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewyang204/NotepadEE/bbe680a2771952db48061e847de6ef5c3911b52d/Windows/Notepad.ico -------------------------------------------------------------------------------- /Windows/README.md: -------------------------------------------------------------------------------- 1 | # Notepad== Windows Version 2 | 3 | This is the repository for the Windows version of this software. 4 | 5 | Main repository: https://www.github.com/matthewyang204/NotepadEE 6 | 7 | This is the Windows version's source code 8 | 9 | The Windows binaries can be downloaded from the releases 10 | 11 | Any PC running Windows XP or later works 12 | 13 | Build instructions: 14 | - Run the command prompt as admin and navigate to the cloned repository. 15 | - Run `dpinstall.bat` in the Command Prompt to find out what's missing. Install the missing component that the batch script specifies if it fails. 16 | - If all dependencies are met, it will automatically use pip3 to install the needed Python libraries. 17 | - Once dependencies are met, run `build.bat`. 18 | - Download and install Inno Setup if you haven't already. Then, open the .iss setup script and build by clicking Build>Compile. Note that you need to use Inno Setup 5 on Windows Vista or earlier. 19 | - Run the installer and you've successfully installed. 20 | 21 | Python 3.4 compatibility scripts: 22 | - There are scripts with `-3.4` appended to their filenames that are compatible with Python 3.4 23 | 24 | Installing from the precompiled binaries: 25 | - Binaries are next to all other binaries 26 | - x64 binaries are provided for users, however, they are not signed 27 | - Version compatibility: 28 | - Windows XP or later; ARM64 systems need Win10 ARM64 or later 29 | - Python 3.4 or later 30 | 31 | Upgrading: 32 | - You can directly run the new installer to upgrade. You can either download this from the Release of the new version or you can install from your custom-built installer. 33 | -------------------------------------------------------------------------------- /Windows/build-3.4.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pyinstaller --noconsole --hidden-import=tkinter -i src\Notepad.ico src\Notepad==.py 3 | copy Notepad.ico "dist\Notepad==\" -------------------------------------------------------------------------------- /Windows/build-debug.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pyinstaller --hide-console hide-early --hidden-import=tkinter -i Notepad.ico src\Notepad==.py -------------------------------------------------------------------------------- /Windows/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pyinstaller --noconsole --hidden-import=tkinter -i src\Notepad.ico src\Notepad==.py 3 | copy Notepad.ico "dist\Notepad==\_internal\" -------------------------------------------------------------------------------- /Windows/clean.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rmdir /s /q dist 3 | rmdir /s /q Output 4 | rmdir /s /q build 5 | del /p /q "Notepad==.spec" 6 | -------------------------------------------------------------------------------- /Windows/dpinstall-3.4.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Check for admin privileges 3 | NET SESSION >nul 2>&1 4 | if %errorLevel% NEQ 0 ( 5 | echo ERROR: This script requires administrative privileges. 6 | echo Exiting... 7 | exit /b 1 8 | ) 9 | 10 | :: Check if Python is installed 11 | python --version >nul 2>&1 12 | if %errorLevel% NEQ 0 ( 13 | echo ERROR: Python is not installed or not in the system PATH. 14 | echo Exiting... 15 | exit /b 1 16 | ) 17 | 18 | :: Install requirements from requirements.txt 19 | echo Installing Python requirements from requirements.txt... 20 | python -m pip install -r requirements-3.4.txt 21 | 22 | :: Check if pip install was successful 23 | if %errorLevel% NEQ 0 ( 24 | echo ERROR: Failed to install Python requirements. 25 | echo Exiting... 26 | exit /b 1 27 | ) 28 | 29 | echo Creating directories... 30 | mkdir /p C:\TMP 31 | echo Exiting... 32 | exit /b 1 33 | 34 | echo Installation completed successfully. 35 | -------------------------------------------------------------------------------- /Windows/dpinstall.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Check for admin privileges 3 | NET SESSION >nul 2>&1 4 | if %errorLevel% NEQ 0 ( 5 | echo ERROR: This script requires administrative privileges. 6 | echo Exiting... 7 | exit /b 1 8 | ) 9 | 10 | :: Check if Python is installed 11 | python --version >nul 2>&1 12 | if %errorLevel% NEQ 0 ( 13 | echo ERROR: Python is not installed or not in the system PATH. 14 | echo Exiting... 15 | exit /b 1 16 | ) 17 | 18 | :: Install requirements from requirements.txt 19 | echo Installing Python requirements from requirements.txt... 20 | python -m pip install -r requirements.txt 21 | 22 | :: Check if pip install was successful 23 | if %errorLevel% NEQ 0 ( 24 | echo ERROR: Failed to install Python requirements. 25 | ) 26 | 27 | echo Creating directories... 28 | mkdir /p C:\TMP 29 | echo Exiting... 30 | exit /b 1 31 | -------------------------------------------------------------------------------- /Windows/notepadee-x64-setup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "Notepad==" 5 | #define MyAppVersion "5.1.0" 6 | #define MyAppPublisher "Matthew Yang" 7 | #define MyAppURL "https://www.github.com/matthewyang204/NotepadEE" 8 | #define MyAppExeName "Notepad==.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{76B5E6F2-7D32-4CC4-986B-7F7ED7A3D836} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={autopf}\{#MyAppName} 22 | ; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run 23 | ; on anything but x64 and Windows 11 on Arm. 24 | ArchitecturesAllowed=x64compatible 25 | ; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the 26 | ; install be done in "64-bit mode" on x64 or Windows 11 on Arm, 27 | ; meaning it should use the native 64-bit Program Files directory and 28 | ; the 64-bit view of the registry. 29 | ArchitecturesInstallIn64BitMode=x64compatible 30 | DisableProgramGroupPage=yes 31 | ; Remove the following line to run in administrative install mode (install for all users.) 32 | PrivilegesRequired=lowest 33 | PrivilegesRequiredOverridesAllowed=dialog 34 | OutputBaseFilename=notepadee-{#MyAppVersion}-x64-setup 35 | SetupIconFile=Notepad.ico 36 | Compression=lzma 37 | SolidCompression=yes 38 | WizardStyle=modern 39 | 40 | [Languages] 41 | Name: "english"; MessagesFile: "compiler:Default.isl" 42 | 43 | [Tasks] 44 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 45 | 46 | [Files] 47 | Source: "dist\Notepad==\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 48 | Source: "dist\Notepad==\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 49 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 50 | 51 | [Icons] 52 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 53 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 54 | 55 | [Run] 56 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 57 | 58 | -------------------------------------------------------------------------------- /Windows/notepadee-x86-setup-is5.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "Notepad==" 5 | #define MyAppVersion "5.1.0" 6 | #define MyAppPublisher "Matthew Yang" 7 | #define MyAppURL "https://www.github.com/matthewyang204/NotepadEE" 8 | #define MyAppExeName "Notepad==.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{76B5E6F2-7D32-4CC4-986B-7F7ED7A3D836} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={pf}\{#MyAppName} 22 | ; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run 23 | ; on anything but x64 and Windows 11 on Arm. 24 | ; ArchitecturesAllowed=x64compatible 25 | ; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the 26 | ; install be done in "64-bit mode" on x64 or Windows 11 on Arm, 27 | ; meaning it should use the native 64-bit Program Files directory and 28 | ; the 64-bit view of the registry. 29 | ; ArchitecturesInstallIn64BitMode=x64compatible 30 | DisableProgramGroupPage=yes 31 | ; Remove the following line to run in administrative install mode (install for all users.) 32 | PrivilegesRequired=admin 33 | ; PrivilegesRequiredOverridesAllowed=dialog 34 | OutputBaseFilename=notepadee-{#MyAppVersion}-x86-setup 35 | SetupIconFile=Notepad.ico 36 | Compression=lzma 37 | SolidCompression=yes 38 | 39 | [Languages] 40 | Name: "english"; MessagesFile: "compiler:Default.isl" 41 | 42 | [Tasks] 43 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 44 | 45 | [Files] 46 | Source: "dist\Notepad==\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 47 | Source: "dist\Notepad==\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 48 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 49 | 50 | [Icons] 51 | Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 52 | Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 53 | 54 | [Run] 55 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 56 | -------------------------------------------------------------------------------- /Windows/requirements-3.4.txt: -------------------------------------------------------------------------------- 1 | pefile==2019.4.18 2 | pyinstaller==3.3.1 3 | pillow 4 | tk 5 | -------------------------------------------------------------------------------- /Windows/requirements.txt: -------------------------------------------------------------------------------- 1 | pyinstaller 2 | pillow 3 | tk -------------------------------------------------------------------------------- /Windows/src/Notepad.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewyang204/NotepadEE/bbe680a2771952db48061e847de6ef5c3911b52d/Windows/src/Notepad.ico -------------------------------------------------------------------------------- /Windows/src/Notepad==.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import filedialog 3 | import os 4 | from tkinter import messagebox 5 | from tkinter import font 6 | import sys 7 | import time 8 | import platform 9 | import subprocess 10 | import threading 11 | # import atexit 12 | import signal 13 | import errno 14 | try: 15 | import idlelib.colorizer as ic 16 | import idlelib.percolator as ip 17 | syntaxHighlighting = True 18 | except ImportError: 19 | syntaxHighlighting = False 20 | import re 21 | import pathlib 22 | 23 | global fileToBeOpened 24 | global openFile 25 | fileToBeOpened = None 26 | openFile = None 27 | 28 | folder_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs') 29 | 30 | global file_open 31 | file_open = 0 32 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 33 | if os.path.exists(last_file_path): 34 | with open(last_file_path, 'r') as file: 35 | current_file = file.read() 36 | if current_file.strip() == '': # Check if the file is empty 37 | file_open = 0 38 | else: 39 | file_open = 1 40 | else: 41 | current_file = "" 42 | file_open = 0 43 | 44 | global last_write 45 | last_write = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write') 46 | 47 | file_written = 0 48 | print("file_written set to " + str(file_written)) 49 | 50 | def setup_prefs(event=None): 51 | global folder_path, last_file_path, last_write 52 | 53 | tmp_path = '/tmp' 54 | 55 | if not os.path.exists(tmp_path): 56 | os.makedirs(tmp_path) 57 | 58 | if not os.path.exists(folder_path): 59 | os.makedirs(folder_path) 60 | 61 | if not os.path.exists(last_file_path): 62 | with open(last_file_path, 'w'): 63 | pass 64 | 65 | if not os.path.exists(last_write): 66 | with open(last_write, 'w'): 67 | pass 68 | 69 | setup_prefs() 70 | 71 | # Define and create, if applicable, a cache folder 72 | cache_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'cache') 73 | if not os.path.exists(cache_path): 74 | os.makedirs(cache_path) 75 | 76 | # Open a log file in write mode 77 | log_file = os.path.join(cache_path, "notepadee_log.txt") 78 | # log_file = os.path.join('/tmp', "notepadee_log.txt") 79 | 80 | # Get current PID 81 | pid = os.getpid() 82 | 83 | # Special printlog statement to print stuff that doesn't belong in a console to the log file 84 | def printlog(message): 85 | with open(log_file, 'a') as file: 86 | file.write("Notepad== at " + str(pid) + ": " + str(message)) 87 | print("Notepad== at " + str(pid) + ": " + str(message)) 88 | 89 | versionInfo = """Notepad==, version 5.1.0 90 | (C) 2024-2025 Matthew Yang""" 91 | 92 | helpInfo = versionInfo + """ 93 | 94 | Usage: notepadee [OPTIONS] [] 95 | 96 | Options: 97 | --version, -v Display version info and exit 98 | --help, -h Display this help message and exit 99 | 100 | Note that [] is not required and if not given, the file that was previously opened will be opened in the new instance. 101 | """ 102 | 103 | arg = sys.argv 104 | if len(arg) <= 1: 105 | pass 106 | else: 107 | if arg[1] == '--version' or arg[1] == '-v': 108 | print(versionInfo) 109 | sys.exit() 110 | elif arg[1] == '--help' or arg[1] == '-h': 111 | print(helpInfo) 112 | sys.exit() 113 | 114 | class platformError(Exception): 115 | pass 116 | 117 | root = tk.Tk() 118 | ask_quit = False 119 | root.title("Notepad==") 120 | root.minsize(800, 600) 121 | root.pack_propagate(False) 122 | 123 | if platform.system() == "Windows": 124 | run_path = os.path.realpath(__file__) 125 | runDir = os.path.dirname(run_path) 126 | app_icon = os.path.join(runDir, 'Notepad.ico') 127 | root.iconbitmap(app_icon) 128 | 129 | status_frame = tk.Frame(root) 130 | status_frame.pack() 131 | 132 | line_var = tk.StringVar() 133 | line_label = tk.Label(status_frame, textvariable=line_var) 134 | line_label.pack(side=tk.LEFT) 135 | 136 | column_var = tk.StringVar() 137 | column_label = tk.Label(status_frame, textvariable=column_var) 138 | column_label.pack(side=tk.LEFT) 139 | 140 | word_count_var = tk.StringVar() 141 | word_count_label = tk.Label(status_frame, textvariable=word_count_var) 142 | word_count_label.pack(side=tk.LEFT) 143 | 144 | text_size_indicator = tk.StringVar() 145 | size_label = tk.Label(status_frame, textvariable=text_size_indicator) 146 | size_label.pack(side=tk.LEFT) 147 | 148 | file_var = tk.StringVar() 149 | file_label = tk.Label(status_frame, textvariable=file_var) 150 | file_label.pack(side=tk.LEFT) 151 | 152 | def get_font_for_platform(): 153 | if os.name == 'nt': 154 | return font.Font(family="Consolas", size=12) 155 | elif os.uname().sysname == 'Darwin': 156 | return font.Font(family="Menlo", size=12) 157 | else: 158 | return font.Font(family="DejaVu Sans Mono", size=12) 159 | 160 | text_font = get_font_for_platform() 161 | text_area = tk.Text(root, width=100, height=80, wrap=tk.WORD, undo=True) 162 | text_area.config(font=text_font) 163 | 164 | if syntaxHighlighting: 165 | try: 166 | cdg = ic.ColorDelegator() 167 | cdg.prog = re.compile(r'\b(?Ptkinter)\b|' + ic.make_pat().pattern, re.S) 168 | cdg.idprog = re.compile(r'\s+(\w+)', re.S) 169 | 170 | cdg.tagdefs['MYGROUP'] = {'foreground': '#7F7F7F', 'background': ''} 171 | 172 | # For platforms with malfunctioning idlelibs, force the standard colors 173 | if platform.system() == "Darwin": 174 | cdg.tagdefs['COMMENT'] = {'foreground': '#dd0000', 'background': ''} # red 175 | cdg.tagdefs['KEYWORD'] = {'foreground': '#F2A061', 'background': ''} # orange 176 | cdg.tagdefs['BUILTIN'] = {'foreground': '#900090', 'background': ''} # purple 177 | cdg.tagdefs['STRING'] = {'foreground': '#00aa00', 'background': ''} # green 178 | cdg.tagdefs['DEFINITION'] = {'foreground': '#000000', 'background': ''} # black 179 | except AttributeError: 180 | cdg = ic.ColorDelegator() 181 | cdg.prog = re.compile(r'\b(?Ptkinter)\b|' + ic.make_pat(), re.S) 182 | cdg.idprog = re.compile(r'\s+(\w+)', re.S) 183 | 184 | cdg.tagdefs['MYGROUP'] = {'foreground': '#7F7F7F', 'background': ''} 185 | 186 | # For platforms with malfunctioning idlelibs, force the standard colors 187 | if platform.system() == "Darwin": 188 | cdg.tagdefs['COMMENT'] = {'foreground': '#dd0000', 'background': ''} # red 189 | cdg.tagdefs['KEYWORD'] = {'foreground': '#F2A061', 'background': ''} # orange 190 | cdg.tagdefs['BUILTIN'] = {'foreground': '#900090', 'background': ''} # purple 191 | cdg.tagdefs['STRING'] = {'foreground': '#00aa00', 'background': ''} # green 192 | cdg.tagdefs['DEFINITION'] = {'foreground': '#000000', 'background': ''} # black 193 | else: 194 | printlog("Platform does not support newer idlelibs, syntax highlighting is disabled") 195 | 196 | text_area.delete(1.0, "end") 197 | with open(last_write, 'r') as file: 198 | text_area.insert(1.0, file.read()) 199 | if platform.system() == "Darwin" or platform.system() == "Linux": 200 | printlog("Clearing any locks...") 201 | subprocess.call(["/bin/rm", "-rf", os.path.join(cache_path, "loadPreviousSave.lock")]) 202 | else: 203 | printlog("We are on a system that does not need or use file locks, skipping...") 204 | 205 | def runonarg(arg): 206 | global file_written, current_file, file_open 207 | if os.path.exists(arg): 208 | with open(arg, 'r') as file: 209 | if file_written == 1: 210 | if platform.system() == "Darwin": 211 | newWindow_macOS(openFile=arg) 212 | elif platform.system() == "Linux": 213 | newWindow_Linux(openFile=arg) 214 | else: 215 | text_area.delete(1.0, "end") 216 | current_file = arg 217 | text_area.insert(1.0, file.read()) 218 | file_open = 1 219 | else: 220 | text_area.delete(1.0, "end") 221 | current_file = arg 222 | text_area.insert(1.0, file.read()) 223 | file_open = 1 224 | #printlog("Current file path: " + current_file) 225 | #printlog("File open: " + str(file_open)) 226 | printlog("File loaded") 227 | else: 228 | raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), arg) 229 | 230 | # text_area.delete(1.0, "end") 231 | # with open(arg, 'w') as file: 232 | # text = text_area.get(1.0, "end-1c") 233 | # file.write(text) 234 | # file_open = 1 235 | # current_file = os.path.abspath(arg) 236 | # #printlog("Current file path: " + current_file) 237 | # #printlog("File open: " + str(file_open)) 238 | # printlog("Because the file doesn't exist, it was created as a blank new file instead") 239 | 240 | # except Exception as e: 241 | # printlog("Exception " + str(e) + " caught!") 242 | 243 | # Check if the system is macOS (Darwin) 244 | if platform.system() == "Darwin": 245 | try: 246 | def addOpenEventSupport(root): 247 | """ 248 | Enable the application to handle macOS 'Open with' events. 249 | """ 250 | fileToBeOpenedPath = os.path.join(cache_path, "fileToBeOpened.txt") 251 | openFilePath = os.path.join(cache_path, "openFile.txt") 252 | 253 | def doOpenFile(*args): 254 | global fileToBeOpened, openFile 255 | if args: 256 | fileToBeOpened = str(args[0]) 257 | openFile = 1 258 | printlog("File was passed from Finder, loading file...") 259 | runonarg(fileToBeOpened) 260 | 261 | else: 262 | fileToBeOpened = "" 263 | openFile = 0 264 | printlog("No file passed from Finder, loading program with last known file...") 265 | printlog("Program loaded") 266 | 267 | printlog("fileToBeOpened: " + str(fileToBeOpened)) 268 | printlog("openFile: " + str(openFile)) 269 | # Hook into macOS-specific file open event 270 | root.createcommand("::tk::mac::OpenDocument", doOpenFile) 271 | 272 | addOpenEventSupport(root) 273 | 274 | except Exception as e: 275 | fileToBeOpened = "" 276 | openFile = 0 277 | printlog(str(e)) 278 | printlog("fileToBeOpened: " + str(fileToBeOpened)) 279 | 280 | else: 281 | # Tell the user through the console that we are running on Linux 282 | printlog("We are running on a standard Linux distro or other OS, falling back to file arguments...") 283 | # If not macOS, fallback to command line arguments 284 | filearg = sys.argv 285 | if len(filearg) <= 1: 286 | openFile = 0 287 | printlog("No arguments provided. Proceeding to load program with last known file...") 288 | printlog("Program loaded") 289 | else: 290 | openFile = 1 291 | printlog("Assuming argument is the file to open. Loading file...") 292 | fileToBeOpened = filearg[1] 293 | runonarg(fileToBeOpened) 294 | 295 | 296 | def debug_var(event=None): 297 | global file_open, current_file 298 | if current_file: 299 | printlog("Current file variable works") 300 | printlog(current_file) 301 | else: 302 | printlog("Not intact") 303 | if file_open: 304 | printlog("File_open variable is intact") 305 | printlog(file_open) 306 | else: 307 | printlog("Not working") 308 | return 'break' 309 | 310 | 311 | def autosave_file(event=None): 312 | global current_file 313 | global file_open 314 | try: 315 | if file_open == 1: 316 | with open(current_file, 'w') as file: 317 | text = text_area.get('1.0', 'end-1c') 318 | file.write(text) 319 | except FileNotFoundError: 320 | return 'break' 321 | printlog("Autosaved file") 322 | 323 | 324 | def write_prefs(event=None): 325 | setup_prefs() 326 | 327 | global current_file, file_open 328 | with open(os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write'), 'w') as file: 329 | file.write(text_area.get('1.0', 'end-1c')) 330 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 331 | with open(last_file_path, 'w') as file: 332 | file.write(str(current_file)) 333 | autosave_file() 334 | printlog("Wrote prefs successfully") 335 | 336 | def spoof_prefs(current_file="", file_open=""): 337 | with open(os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_write'), 'w') as file: 338 | file.write(text_area.get('1.0', 'end-1c')) 339 | last_file_path = os.path.join(os.path.expanduser('~'), '.notepadee', 'prefs', 'last_file_path') 340 | with open(last_file_path, 'w') as file: 341 | file.write(str(current_file)) 342 | autosave_file() 343 | printlog("Wrote prefs successfully") 344 | 345 | # save_as provides the dialog 346 | def save_as(event=None): 347 | global current_file, file_open 348 | file_path = filedialog.asksaveasfilename( 349 | defaultextension="", 350 | filetypes=( 351 | ("All Files", "*.*"), 352 | 353 | # Plain text files 354 | ("Plain text file", ".txt"), 355 | ("Log file", ".log"), 356 | 357 | # ms ini/inf 358 | ("INI file", ".ini"), 359 | ("INF file (.inf)", ".inf"), 360 | 361 | # C, C++, objc 362 | ("C, C++, objc header", ".h"), 363 | ("C, C++, objc header", ".hh"), 364 | ("C, C++, objc header", ".hpp"), 365 | ("C, C++, objc header", ".hxx"), 366 | ("C, C++, objc source", ".c"), 367 | ("C, C++, objc source", ".cpp"), 368 | ("C, C++, objc source", ".cxx"), 369 | ("C, C++, objc source", ".cc"), 370 | ("C, C++, objc source", ".m"), 371 | ("C, C++, objc source", ".mm"), 372 | ("C, C++, objc project", ".vcxproj"), 373 | ("C, C++, objc project", ".vcproj"), 374 | ("C, C++, objc properties", ".props"), 375 | ("C, C++, objc properties", ".vsprops"), 376 | ("C, C++, objc manifest", ".manifest"), 377 | 378 | # Java, C#, Pascal 379 | ("Java file", ".java"), 380 | ("Pascal file", ".pas"), 381 | ("Pascal file", ".pp"), 382 | ("Include file", ".inc"), 383 | 384 | # .NET code 385 | ("Visual Basic (.vb)", ".vb"), 386 | ("Visual Basic script (.vbs)", ".vbs"), 387 | ("C# file (.cs)", ".cs"), 388 | 389 | # Web script files 390 | ("HTML file", ".html"), 391 | ("HTML file", ".htm"), 392 | ("Server-side HTML", ".shtml"), 393 | ("Server-side HTML", ".shtm"), 394 | ("HTML Application", ".hta"), 395 | ("ASP file", ".asp"), 396 | ("ASP.NET file", ".aspx"), 397 | ("CSS file", ".css"), 398 | ("JavaScript file", ".js"), 399 | ("JSON file", ".json"), 400 | ("JavaScript module", ".mjs"), 401 | ("JavaScript module", ".jsm"), 402 | ("JSP file", ".jsp"), 403 | ("PHP file", ".php"), 404 | ("PHP3 file", ".php3"), 405 | ("PHP4 file", ".php4"), 406 | ("PHP5 file", ".php5"), 407 | ("PHP script", ".phps"), 408 | ("PHP script", ".phpt"), 409 | ("PHP file", ".phtml"), 410 | ("XML file", ".xml"), 411 | ("XHTML file", ".xhtml"), 412 | ("XHTML file", ".xht"), 413 | ("XUL file", ".xul"), 414 | ("KML file", ".kml"), 415 | ("XAML file", ".xaml"), 416 | ("XSML file", ".xsml"), 417 | 418 | # Script files 419 | ("Shell script", ".sh"), 420 | ("Bash script", ".bsh"), 421 | ("Bash script", ".bash"), 422 | ("Batch file", ".bat"), 423 | ("Command file", ".cmd"), 424 | ("NSIS script", ".nsi"), 425 | ("NSIS header", ".nsh"), 426 | ("Lua script", ".lua"), 427 | ("Perl script", ".pl"), 428 | ("Perl module", ".pm"), 429 | ("Python script", ".py"), 430 | ("Inno Setup script", ".iss"), 431 | ("Makefile", ".mak"), 432 | 433 | # Property scripts 434 | ("Resource file", ".rc"), 435 | ("ActionScript", ".as"), 436 | ("MaxScript", ".mx"), 437 | 438 | # Fortran, TeX, SQL 439 | ("Fortran file", ".f"), 440 | ("Fortran file", ".for"), 441 | ("Fortran 90 file", ".f90"), 442 | ("Fortran 95 file", ".f95"), 443 | ("Fortran 2000 file", ".f2k"), 444 | ("TeX file", ".tex"), 445 | ("SQL file", ".sql"), 446 | 447 | # Miscellaneous files 448 | ("NFO file", ".nfo"))) 449 | current_file = file_path 450 | # if file_path doesn't exist, let's stop the function and return False 451 | if not file_path: 452 | return False 453 | 454 | # if we get a valid file_path, let's save via dialog 455 | try: 456 | printlog("Saving file to location:") 457 | printlog(file_path) 458 | with open(file_path, 'w') as file: 459 | text = text_area.get(1.0, "end-1c") 460 | file.write(text) 461 | write_prefs() 462 | file_open = 1 463 | printlog("File was saved to different location successfully.") 464 | return True 465 | 466 | # if any errors manage to get past this, let's do an exception to quit gracefully 467 | except FileNotFoundError: 468 | messagebox.showerror("Error", "Location nonexistent") 469 | return False 470 | 471 | def open_file(event=None): 472 | global current_file, file_open 473 | save_file("y") 474 | file_path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")]) 475 | if file_path: 476 | text_area.delete(1.0, "end") 477 | current_file = file_path 478 | with open(file_path, 'r') as file: 479 | text_area.insert(1.0, file.read()) 480 | file_open = 1 481 | printlog("New file opened") 482 | write_prefs() 483 | 484 | def open_file_v2(event=None): 485 | global current_file, file_written, file_open 486 | save_file("y") 487 | file_path = filedialog.askopenfilename(filetypes=[("All Files", "*.*")]) 488 | if file_path: 489 | with open(file_path, 'r') as file: 490 | if file_written == 1: 491 | if platform.system() == "Darwin": 492 | newWindow_macOS(openFile=file_path) 493 | elif platform.system() == "Linux": 494 | newWindow_Linux(openFile=file_path) 495 | else: 496 | text_area.delete(1.0, "end") 497 | current_file = file_path 498 | text_area.insert(1.0, file.read()) 499 | file_open = 1 500 | else: 501 | text_area.delete(1.0, "end") 502 | current_file = file_path 503 | text_area.insert(1.0, file.read()) 504 | file_open = 1 505 | printlog("New file opened") 506 | # write_prefs() 507 | 508 | def save_file(warn): 509 | global current_file, file_open, file_written 510 | if file_open == 1: 511 | try: 512 | debug_var() 513 | with open(current_file, 'w') as file: 514 | text = text_area.get('1.0', 'end-1c') 515 | file.write(text) 516 | write_prefs() 517 | return True 518 | except FileNotFoundError: 519 | return 'break' 520 | else: 521 | if warn == "y": 522 | response = messagebox.askyesno("Warning: File is not saved","The current file is not saved. Do you want to save it to a selected location?") 523 | if response: 524 | if save_as(): 525 | printlog("File saved without warning") 526 | return True 527 | else: 528 | return True 529 | elif warn == "w": 530 | if file_written == 1: 531 | response = messagebox.askyesno("Warning: File is not saved","The current file is not saved. Changes may be lost if they are not saved. Do you want to save before exiting?") 532 | if response: 533 | if save_as(): 534 | printlog("File saved") 535 | return True 536 | else: 537 | return True 538 | else: 539 | response = messagebox.askyesno("Create new file","The file does not exist. Do you want to create it as a new file before proceeding?") 540 | if response: 541 | if save_as(): 542 | printlog("File saved after warning user") 543 | return True 544 | else: 545 | return True 546 | 547 | def save_file2(event=None): 548 | global current_file, file_open, file_written 549 | printlog("No-warning wrapper triggered, running save_file with nowarning option") 550 | save_file("n") 551 | 552 | def new_file(event=None): 553 | global current_file, file_open, file_written 554 | 555 | # Check if there is text in text_area 556 | if file_written == 1: 557 | # Only run this code if save_file, otherwise, don't force user to clear 558 | if save_file("y"): 559 | text_area.delete(1.0, "end") 560 | printlog("Cleared text_area") 561 | current_file = "" 562 | write_prefs() 563 | file_open = 0 564 | printlog("New file created") 565 | file_written = 0 566 | 567 | # Otherwise, clear without obstruction 568 | else: 569 | text_area.delete(1.0, "end") 570 | printlog("Cleared text_area") 571 | current_file = "" 572 | 573 | 574 | def cut_text(event=None): 575 | text_area.clipboard_clear() 576 | text_area.clipboard_append(text_area.get("sel.first", "sel.last")) 577 | text_area.delete("sel.first", "sel.last") 578 | printlog("Cut option succeeded") 579 | return 'break' 580 | 581 | 582 | def copy_text(event=None): 583 | text_area.clipboard_clear() 584 | text_area.clipboard_append(text_area.get("sel.first", "sel.last")) 585 | printlog("Text copied to clipboard") 586 | return 'break' 587 | 588 | 589 | def paste_text(event=None): 590 | text_area.insert("insert", text_area.clipboard_get()) 591 | printlog("Text pasted from clipboard") 592 | return 'break' 593 | 594 | 595 | def select_all_text(event=None): 596 | text_area.tag_add("sel", "1.0", "end") 597 | printlog("Text selected") 598 | return 'break' 599 | 600 | 601 | def undo(event=None): 602 | try: 603 | text_area.edit_undo() 604 | except tk.TclError: 605 | pass 606 | printlog("Edit undone") 607 | 608 | 609 | def redo(event=None): 610 | try: 611 | text_area.edit_redo() 612 | except tk.TclError: 613 | pass 614 | printlog("Edit redone") 615 | 616 | 617 | def find_and_replace(event=None): 618 | popup = tk.Toplevel(root) 619 | popup.title("Find and Replace") 620 | 621 | find_label = tk.Label(popup, text="Enter the text you want to find:") 622 | find_label.pack() 623 | find_entry = tk.Entry(popup) 624 | find_entry.pack() 625 | 626 | replace_label = tk.Label( 627 | popup, text="Enter the text you want to replace it with:") 628 | replace_label.pack() 629 | replace_entry = tk.Entry(popup) 630 | replace_entry.pack() 631 | 632 | def perform_replace(event=None): 633 | find_text = find_entry.get() 634 | replace_text = replace_entry.get() 635 | 636 | text_widget = text_area.get("1.0", tk.END) 637 | if find_text: 638 | text_widget = text_widget.replace(find_text, replace_text) 639 | text_area.delete("1.0", tk.END) 640 | text_area.insert(tk.END, text_widget) 641 | 642 | def close(event=None): 643 | popup.destroy() 644 | 645 | replace_button = tk.Button(popup, text="Replace", command=perform_replace) 646 | close_button = tk.Button(popup, text="Close", command=close) 647 | replace_button.pack() 648 | close_button.pack() 649 | find_entry.bind('', perform_replace) 650 | replace_entry.bind('', perform_replace) 651 | 652 | def go_to_line(event=None): 653 | popup = tk.Toplevel(root) 654 | popup.title("Go To Line") 655 | 656 | line_number_label = tk.Label(popup, text="Enter the line that you want to go to:") 657 | line_number_label.pack() 658 | entrybox = tk.Entry(popup) 659 | entrybox.pack() 660 | 661 | def go(event=None): 662 | line_number = entrybox.get() 663 | text_area.mark_set("insert", str(line_number) + ".0") 664 | 665 | def close(event=None): 666 | popup.destroy() 667 | 668 | go_to_line_button = tk.Button(popup, text="Go", command=go) 669 | close_button = tk.Button(popup, text="Close", command=close) 670 | go_to_line_button.pack() 671 | close_button.pack() 672 | entrybox.bind('', go) 673 | 674 | def cPos(index): 675 | line, column = text_area.index("insert").split(".") 676 | 677 | if index == "both": 678 | return line, column 679 | elif index == "line": 680 | return line 681 | elif index == "column": 682 | return column 683 | else: 684 | printlog("invalidArg") 685 | return "invalidArg" 686 | 687 | def findNext(text): 688 | try: 689 | last_highlight = text_area.index("highlight.last") 690 | start = last_highlight 691 | except tk.TclError: 692 | cPos_line, cpos_column = cPos("both") 693 | start = str(cPos_line) + "." + str(cPos_column) 694 | # start= "1.0" 695 | 696 | text_area.tag_remove("highlight", "1.0", "end") 697 | try: 698 | start = text_area.search(text, start, stopindex="end") 699 | end = str(start) + " + " + str(len(text)) + "c" 700 | text_area.tag_add("highlight", start, end) 701 | except Exception as e: 702 | start = "1.0" 703 | start = text_area.search(text, start, stopindex="end") 704 | end = str(start) + " + " + str(len(text)) + "c" 705 | text_area.tag_add("highlight", start, end) 706 | 707 | text_area.tag_config("highlight", background="yellow") 708 | 709 | def find_text(event=None): 710 | popup = tk.Toplevel(root) 711 | popup.title("Find") 712 | 713 | line_number_label = tk.Label(popup, text="Enter the text that you want to find:") 714 | line_number_label.pack() 715 | entrybox = tk.Entry(popup) 716 | entrybox.pack() 717 | 718 | def findNext_wrapper(event=None): 719 | findNext(entrybox.get()) 720 | 721 | def clear(event=None): 722 | text_area.tag_remove("highlight", "1.0", "end") 723 | 724 | def close(event=None): 725 | clear() 726 | popup.destroy() 727 | 728 | find_button = tk.Button(popup, text="Find Next", command = findNext_wrapper) 729 | close_button = tk.Button(popup, text="Close", command=close) 730 | clear_button = tk.Button(popup, text="Clear", command=clear) 731 | find_button.pack() 732 | clear_button.pack() 733 | close_button.pack() 734 | entrybox.bind('', findNext_wrapper) 735 | 736 | def mark_text(event=None): 737 | selectStart = text_area.index("sel.first") 738 | selectEnd = text_area.index("sel.last") 739 | # DO NOT enable this 740 | # printlog(f"Current selection is {selectStart}, {selectEnd}") 741 | printlog("Clearing all current highlights in selection...") 742 | text_area.tag_remove("highlight_permanent", selectStart, selectEnd) 743 | printlog("Configuring highlight_permanent tags to selection...") 744 | text_area.tag_add("highlight_permanent", selectStart, selectEnd) 745 | printlog("Configuring tagged text to highlight...") 746 | text_area.tag_config("highlight_permanent", background="green") 747 | printlog("done") 748 | 749 | def unmark_text(event=None): 750 | selectStart = text_area.index("sel.first") 751 | selectEnd = text_area.index("sel.last") 752 | # DO NOT enable this 753 | # printlog(f"Current selection is {selectStart}, {selectEnd}") 754 | printlog("Clearing all current highlights in selection...") 755 | text_area.tag_remove("highlight_permanent", selectStart, selectEnd) 756 | printlog("done") 757 | 758 | def unmark_all_text(event=None): 759 | printlog("Clearing all current highlights...") 760 | text_area.tag_remove("highlight_permanent", "1.0", "end") 761 | printlog("done") 762 | 763 | def update_line_number(event=None): 764 | line, column = text_area.index(tk.INSERT).split('.') 765 | line_var.set("Line: " + line) 766 | column_var.set("Column: " + column) 767 | words = text_area.get(1.0, 'end-1c').split() 768 | word_count_var.set("Words: " + str(len(words))) 769 | file_var.set("File: " + os.path.basename(current_file)) 770 | if current_file: 771 | root.title(str(current_file) + " - Notepad==") # f"{current_file} - Notepad==" 772 | else: 773 | root.title("Notepad==") 774 | text_size = text_font['size'] 775 | text_size_indicator.set("Size: " + str(text_size)) # f"Size: {text_size}" 776 | # print("Status bar updated") 777 | root.after(100, update_line_number) 778 | 779 | def applySyntaxHighlighting(event=None): 780 | global current_file, syntaxHighlighting 781 | pythonExts = ['.py', '.pyw', '.pyc', '.pyo', '.pyd', '.pyx', '.pxd', '.pxi', '.pyi', '.ipynb', '.pyz'] 782 | if syntaxHighlighting: 783 | try: 784 | if pathlib.Path(os.path.basename(current_file)).suffix in pythonExts: 785 | ip.Percolator(text_area).insertfilter(cdg) 786 | else: 787 | if getattr(cdg, 'delegate', None) is not None: 788 | ip.Percolator(text_area).removefilter(cdg) 789 | except Exception as e: 790 | if getattr(cdg, 'delegate', None) is not None: 791 | ip.Percolator(text_area).removefilter(cdg) 792 | 793 | else: 794 | printlog("Python version does not support syntax highlighting") 795 | 796 | def increase_font_size(event=None): 797 | current_size = text_font['size'] 798 | text_font.config(size=current_size + 1) 799 | printlog("Font size increased by 1 pt") 800 | 801 | def decrease_font_size(event=None): 802 | current_size = text_font['size'] 803 | text_font.config(size=current_size - 1) 804 | printlog("Font size decreased by 1 pt") 805 | 806 | # Create a function to check for text in text_area 807 | def check_file_written(event=None): 808 | global file_written 809 | printlog("Checking if text_area has been edited by the user to contain text...") 810 | current_text = text_area.get(1.0, "end-1c") 811 | # if there is text, set it to 1 812 | if current_text: 813 | printlog("There is text; setting to 1") 814 | file_written = 1 815 | # otherwise, set it to 0 816 | else: 817 | printlog("No text") 818 | file_written = 0 819 | 820 | def runinbackground(event=None): 821 | write_prefs() 822 | check_file_written() 823 | applySyntaxHighlighting() 824 | debug_var() 825 | 826 | def newWindow_macOS(openFile=""): 827 | global folder_path 828 | if platform.system() == "Darwin": 829 | run_path = os.path.realpath(__file__) 830 | cwd = os.getcwd() 831 | freeze_time = 1 832 | emptyString = "" 833 | # printlog(f"Script path is {run_path}") 834 | # printlog(f"Current working directory is {cwd}") 835 | # printlog(f"App is located at {cwd}/Notepad==.app") 836 | # DO NOT enable this 837 | # printlog(f"Creating a lock file at {os.path.join(cache_path, "loadPreviousSave.lock")}...") 838 | with open(os.path.join(cache_path, "loadPreviousSave.lock"), "w") as file: 839 | file.write(emptyString) 840 | # DO NOT enable this 841 | # printlog(f"Clearing the prefs folder at {folder_path} to ensure new instance loads up with new file...") 842 | subprocess.call(["/bin/rm", "-rf", folder_path]) 843 | printlog("Launching new instance...") 844 | if openFile: 845 | subprocess.call(["/usr/bin/open", "-n", "-a", cwd + "/Notepad==.app", openFile]) 846 | else: 847 | subprocess.call(["/usr/bin/open", "-n", "-a", cwd + "/Notepad==.app"]) 848 | # DO NOT enable this 849 | # printlog(f"Waiting for {os.path.join(cache_path, "loadPreviousSave.lock")}...") 850 | while os.path.exists(os.path.join(cache_path, "loadPreviousSave.lock")): 851 | pass 852 | # DO NOT enable this 853 | # printlog(f"Writing cache back to prefs folder at {folder_path}...") 854 | write_prefs() 855 | printlog("done") 856 | else: 857 | raise platformError("This function is only designed to be run on macOS. We do not understand why you would want this function to run anyway, nor how you got it to run. The function needs to be specific to the platform.") 858 | 859 | def newWindow_Linux(openFile=""): 860 | def main(event=None): 861 | global folder_path 862 | run_path = os.path.realpath(__file__) 863 | cwd = os.getcwd() 864 | pyexe = sys.executable 865 | pyexe_dir = os.path.dirname(pyexe) 866 | pyInstFile = os.path.join(pyexe_dir, '.pyinstaller') 867 | freeze_time = 1 868 | 869 | # DO NOT enable 870 | # printlog(f"Script path is {run_path}") 871 | # printlog(f"Current working directory is {cwd}") 872 | # printlog(f"Executable is located at {pyexe}") 873 | emptyString = "" 874 | 875 | # DO NOT enable, this is only compatible with Python 3.12 and later 876 | # printlog(f"Creating a lock file at {os.path.join(cache_path, "loadPreviousSave.lock")}...") 877 | with open(os.path.join(cache_path, "loadPreviousSave.lock"), "w") as file: 878 | file.write(emptyString) 879 | # DO NOT enable 880 | # printlog(f"Clearing the prefs folder at {folder_path} to ensure new instance loads up with new file...") 881 | subprocess.call(["/bin/rm", "-rf", folder_path]) 882 | printlog("Launching new instance...") 883 | # Regular launcher with no open file support 884 | def launcher(): 885 | if os.path.exists(pyInstFile): 886 | printlog("We are running in PyInstaller mode, running only the executable...") 887 | subprocess.Popen([pyexe], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 888 | else: 889 | printlog("We are probably running in standard interpreted mode, launching executable with python file...") 890 | subprocess.Popen([pyexe, run_path], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 891 | # launcher with open file support 892 | def launcher2(): 893 | if os.path.exists(pyInstFile): 894 | printlog("We are running in PyInstaller mode, running only the executable...") 895 | subprocess.Popen([pyexe, openFile], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 896 | else: 897 | printlog("We are probably running in standard interpreted mode, launching executable with python file...") 898 | subprocess.Popen([pyexe, run_path, openFile], preexec_fn=os.setsid, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) 899 | if openFile: 900 | new_thread = threading.Thread(target=launcher2) 901 | else: 902 | new_thread = threading.Thread(target=launcher) 903 | new_thread.start() 904 | # DO NOT enable, this is only compatible with Python 3.12 and later 905 | # printlog(f"Waiting for {os.path.join(cache_path, "loadPreviousSave.lock")}...") 906 | while os.path.exists(os.path.join(cache_path, "loadPreviousSave.lock")): 907 | pass 908 | # DO NOT enable 909 | # printlog(f"Writing cache back to prefs folder at {folder_path}...") 910 | write_prefs() 911 | printlog("done") 912 | if platform.system() == "Linux": 913 | main() 914 | else: 915 | raise platformError("This function is only designed to be run on Linux. We do not understand why you would want this function to run anyway, nor how you got it to run. The function needs to be specific to the platform.") 916 | 917 | def newWindow(event=None): 918 | if platform.system() == "Darwin": 919 | newWindow_macOS() 920 | elif platform.system() == "Linux": 921 | newWindow_Linux() 922 | else: 923 | raise platformError("There is no newWindow function available for your platform.") 924 | 925 | def exit_handler(event=None): 926 | printlog("Telling user to save file before exit...") 927 | save_file("w") 928 | printlog("Exiting...") 929 | sys.exit() 930 | 931 | def exit_on_keyboardInterrupt(signum, frame): 932 | printlog("Received KeyboardInterrupt, running exit handler...") 933 | exit_handler() 934 | 935 | text_area.pack(fill=tk.BOTH, expand=tk.YES) 936 | text_area.bind('', runinbackground) 937 | text_area.bind('', runinbackground) 938 | runinbackground() 939 | update_line_number() 940 | 941 | menu = tk.Menu(root) 942 | root.config(menu=menu) 943 | 944 | file_menu = tk.Menu(menu) 945 | menu.add_cascade(label="File", menu=file_menu) 946 | file_menu.add_command(label="New", command=new_file) 947 | # if platform.system() == "Darwin": 948 | # file_menu.add_command(label="New Window", command=newWindow_macOS) 949 | # elif platform.system() == "Linux": 950 | # file_menu.add_command(label="New Window", command=newWindow_Linux) 951 | # if platform.system() == "Darwin": 952 | # file_menu.add_command(label="New", command=newWindow) 953 | # elif platform.system() == "Linux": 954 | # file_menu.add_command(label="New", command=newWindow) 955 | file_menu.add_command(label="Open...", command=open_file_v2) 956 | file_menu.add_command(label="Save", command=save_file2) 957 | file_menu.add_command(label="Save as...", command=save_as) 958 | 959 | edit_menu = tk.Menu(menu) 960 | menu.add_cascade(label="Edit", menu=edit_menu) 961 | edit_menu.add_command(label="Cut", command=cut_text) 962 | edit_menu.add_command(label="Copy", command=copy_text) 963 | edit_menu.add_command(label="Paste", command=paste_text) 964 | edit_menu.add_command(label="Select All", command=select_all_text) 965 | edit_menu.add_separator() 966 | edit_menu.add_command(label="Undo", command=undo) 967 | edit_menu.add_command(label="Redo", command=redo) 968 | edit_menu.add_separator() 969 | edit_menu.add_command(label="Mark Text", command=mark_text) 970 | edit_menu.add_command(label="Unmark Text", command=unmark_text) 971 | edit_menu.add_command(label="Unmark All Text", command=unmark_all_text) 972 | edit_menu.add_separator() 973 | edit_menu.add_command(label="Find", command=find_text) 974 | edit_menu.add_command(label="Find and Replace", command=find_and_replace) 975 | edit_menu.add_separator() 976 | edit_menu.add_command(label="Go To Line", command=go_to_line) 977 | 978 | accessibility_menu = tk.Menu(menu) 979 | menu.add_cascade(label="Accessibility", menu=accessibility_menu) 980 | accessibility_menu.add_command(label="Zoom in", command=increase_font_size) 981 | accessibility_menu.add_command(label="Zoom out", command=decrease_font_size) 982 | 983 | window_menu = tk.Menu(menu) 984 | menu.add_cascade(label="Window", menu=window_menu) 985 | window_menu.add_command(label="Close", command=exit_handler) 986 | 987 | if platform.system() == "Darwin": 988 | root.bind_all("", exit_handler) 989 | root.bind_all("", exit_handler) 990 | 991 | root.bind_all('', new_file) 992 | # if platform.system() == "Darwin": 993 | # root.bind_all('', newWindow) 994 | # root.bind_all('', newWindow) 995 | # elif platform.system() == "Linux": 996 | # root.bind_all('', newWindow) 997 | # root.bind_all('', newWindow) 998 | root.bind_all('', open_file_v2) 999 | root.bind_all('', save_file) 1000 | root.bind_all('', save_as) 1001 | 1002 | text_area.bind('', cut_text) 1003 | text_area.bind('', copy_text) 1004 | text_area.bind('', paste_text) 1005 | text_area.bind('', select_all_text) 1006 | text_area.bind('', undo) 1007 | if platform.system() == "Darwin": 1008 | text_area.bind('', redo) 1009 | else: 1010 | text_area.bind('', redo) 1011 | text_area.bind('', mark_text) 1012 | text_area.bind('', unmark_text) 1013 | text_area.bind('', unmark_all_text) 1014 | text_area.bind('', find_text) 1015 | text_area.bind('', find_and_replace) 1016 | text_area.bind('', go_to_line) 1017 | 1018 | text_area.bind('', increase_font_size) 1019 | text_area.bind('', decrease_font_size) 1020 | 1021 | # atexit.register(exit_handler) 1022 | signal.signal(signal.SIGINT, exit_on_keyboardInterrupt) 1023 | signal.signal(signal.SIGTERM, exit_on_keyboardInterrupt) 1024 | root.protocol('WM_DELETE_WINDOW', exit_handler) 1025 | 1026 | # Implement quit event if macOS 1027 | if platform.system() == "Darwin": 1028 | root.createcommand('::tk::mac::Quit', exit_handler) 1029 | 1030 | write_prefs() 1031 | root.mainloop() 1032 | -------------------------------------------------------------------------------- /macOS/._.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewyang204/NotepadEE/bbe680a2771952db48061e847de6ef5c3911b52d/macOS/._.DS_Store -------------------------------------------------------------------------------- /macOS/README.md: -------------------------------------------------------------------------------- 1 | # Notepad== macOS Version 2 | This is the repository for the macOS version of this software. 3 | 4 | Main repository: https://www.github.com/matthewyang204/NotepadEE 5 | 6 | Any Mac capable of running Python 3.4 works for building. Therefore, your Mac must be capable of running OS X 10.5 Leopard or later, as it is the earliest version of macOS supported by Python 3.4. 7 | 8 | Hackintoshes are supported. 9 | 10 | I have separate binaries for Intel and Apple Silicon macs. Please download the correct one. I have signed it with a self-signed signature and can't afford the full Apple Developer notarization. On macOS Sonoma or below, you can bypass the warnings by right-clicking the app and selecting "Open". On macOS Sequioa or later, you will need to disable Gatekeeper entirely by running `sudo spctl --master-disable` and then selecting "Anywhere" at the bottom of the Privacy & Security section of the settings in the "Allow apps from" setting. Alternatively, you may want to use homebrew. 11 | 12 | To use it, first, tap my homebrew repo by running `brew tap matthewyang204/homebrew-formulae-casks`. After this, you can install the cask with `brew install --cask --no-quarantine notepadee`. 13 | 14 | All of the prebuilt binaries provided in the README support macOS Catalina or newer. 15 | 16 | # Signing info for prebuilt binaries: 17 | - Self-signed signature 18 | - Name of signature is "Matthew Yang" 19 | 20 | # Prebuild requirements 21 | - Python 3.4 - 3.12 22 | - make installed 23 | - `configure` script will automatically install Python packages 24 | 25 | # Build instructions 26 | 1. Clone the repository and navigate to the macOS folder inside of it 27 | 2. Run `chmod +x autogen.sh` to give the script execute permissions. 28 | 3. Run `./autogen.sh --arch=` to automatically configure and build. Note that --arch is optional and if you only want to compile for your machine's native architecture, just run `./autogen.sh`. 29 | 4. After you're done compiling, you can use `sudo make install` to install. 30 | -------------------------------------------------------------------------------- /macOS/autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set +x 3 | 4 | if [ "$1" == "clean" ]; then 5 | echo "Received 'clean' argument, removing everything copied over for building..." 6 | make clean 7 | rm -f Makefile 8 | rm -f Notepad.png 9 | rm -f configure 10 | rm -f notepadee.desktop 11 | rm -f requirements.txt 12 | rm -f requirements-3.4.txt 13 | rm -rf src 14 | rm -rf resources 15 | rm -rf .configured 16 | echo "done" 17 | exit 18 | fi 19 | 20 | if [ "$1" == "rebuild" ]; then 21 | echo "Received 'rebuild' argument, removing everything copied over for building and then doing a fresh build..." 22 | make clean 23 | rm -f Makefile 24 | rm -f Notepad.png 25 | rm -f configure 26 | rm -f notepadee.desktop 27 | rm -f requirements.txt 28 | rm -f requirements-3.4.txt 29 | rm -rf src 30 | rm -rf resources 31 | rm -rf .configured 32 | echo "done" 33 | fi 34 | 35 | # First copy files from the Linux section of the repository 36 | echo "Copying files..." 37 | cp -R -v ../Linux/* ./ 38 | echo "done" 39 | 40 | # Debugging exit to check if files have been properly copied 41 | # Only enable if you need it 42 | # exit 0 43 | 44 | # Patch the files 45 | echo "Patching files..." 46 | echo "Removing dpinstall..." 47 | rm dpinstall 48 | echo "Removing resources" 49 | rm -rf resources 50 | echo "Patching configure script..." 51 | rm configure 52 | cp patches/configure ./ 53 | echo "Patching Makefile..." 54 | rm Makefile 55 | cp patches/Makefile ./ 56 | echo "Patching the key mappings..." 57 | sed -i '' 's/` to automatically configure and build. Note that --arch is optional and if you only want to compile for your machine's native architecture, just run `./autogen.sh`. 29 | 4. After you're done compiling, you can use `sudo make install` to install. 30 | -------------------------------------------------------------------------------- /macOS/patches/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check for Python 3 4 | printf "Checking for python3..." 5 | if ! command -v python3 &> /dev/null 6 | then 7 | echo "not found" 8 | echo "Python 3 not found. Please install it to continue." 9 | exit 1 10 | else 11 | echo "found" 12 | fi 13 | 14 | # Check Python 3 version 15 | printf "Checking python3 version..." 16 | version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') 17 | echo $version 18 | 19 | # version=3.11.5 20 | # version=3.13.2 21 | # version=3.12 22 | # if [[ $version == 3.12.* ]]; then 23 | # echo "correct" 24 | # elif [[ $version == 3.13.* ]]; then 25 | # echo "correct" 26 | # else 27 | # echo "incorrect" 28 | # echo "The version of Python 3 currently installed is not a 3.12 release. Please install a 3.12 release to continue." 29 | # exit 1 30 | # fi 31 | 32 | # Set requirements file 33 | printf "Setting requirements.txt..." 34 | if [[ $version == 3.4.* ]]; then 35 | requirements='requirements-3.4.txt' 36 | elif [[ $version == 3.5.x ]]; then 37 | requirements='requirements-3.4.txt' 38 | else 39 | requirements='requirements.txt' 40 | fi 41 | echo "done" 42 | 43 | # Check for pip3 44 | printf "Checking for pip3..." 45 | if ! command -v pip3 &> /dev/null 46 | then 47 | echo "not found" 48 | echo "pip3 not found. Install it to continue." 49 | exit 1 50 | else 51 | echo "found" 52 | fi 53 | 54 | # Check for tkinter as it is required 55 | printf "Checking if python3 supports tkinter..." 56 | if ! command -v python3 -c 'import tkinter' &> /dev/null 57 | then 58 | echo "not available" 59 | echo "Tkinter not available. It is a core framework and required for compilation. Please compile python with tkinter or install a package containing tkinter to continue." 60 | else 61 | echo "available" 62 | fi 63 | 64 | # Check for idlelib 65 | printf "Checking if python3 supports idlelib..." 66 | if ! command -v python3 -c 'import idlelib' &> /dev/null 67 | then 68 | echo "not available" 69 | echo "IDLElib is not available. It is required for syntax highlighting. Please install it to continue." 70 | exit 1 71 | else 72 | echo "available" 73 | fi 74 | 75 | if ! command -v tar &> /dev/null 76 | then 77 | echo "Tar not found. Install it before continuing." 78 | exit 1 79 | else 80 | echo "Tar is installed." 81 | fi 82 | 83 | # Install required python packages 84 | echo "Installing required packages" 85 | pip3 install --upgrade -r $requirements --break-system-packages 86 | echo "done" 87 | 88 | touch .configured 89 | echo "Now run 'make' to build." 90 | 91 | # Force a failure at the end of the script for testing purposes; uncomment these lines to purposely crash the configure script 92 | # echo "Simulating failure for testing..." 93 | # exit 1 94 | -------------------------------------------------------------------------------- /macOS/patches/requirements-3.4.txt: -------------------------------------------------------------------------------- 1 | tk 2 | nuitka==2.6.8 3 | imageio 4 | setuptools 5 | pyyaml -------------------------------------------------------------------------------- /macOS/patches/requirements.txt: -------------------------------------------------------------------------------- 1 | tk 2 | nuitka==2.6.8 3 | imageio 4 | setuptools 5 | pyyaml --------------------------------------------------------------------------------