From 1ee75d3799b62a7e3ddb3a9037d6d6c186b384cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parodi=2C=20Eugenio=20=F0=9F=8C=B6?= Date: Fri, 4 Apr 2025 00:20:47 +0100 Subject: [PATCH] chore: added ttkode among the apps --- .github/workflows/release.yml | 27 +++- .release-please-config.json | 3 + .release-please-manifest.json | 1 + .vscode/launch.json | 11 ++ README.md | 3 + apps/ttkode/README.md | 40 +++++ apps/ttkode/pyproject.toml | 46 ++++++ apps/ttkode/ttkode/__init__.py | 30 ++++ apps/ttkode/ttkode/__main__.py | 28 ++++ apps/ttkode/ttkode/app/__init__.py | 29 ++++ apps/ttkode/ttkode/app/about.py | 59 ++++++++ apps/ttkode/ttkode/app/cfg.py | 56 +++++++ apps/ttkode/ttkode/app/kodeformatter.py | 142 ++++++++++++++++++ apps/ttkode/ttkode/app/kodetextdocument.py | 165 +++++++++++++++++++++ apps/ttkode/ttkode/app/kodetextedit.py | 31 ++++ apps/ttkode/ttkode/app/main.py | 158 ++++++++++++++++++++ apps/ttkode/ttkode/app/options.py | 74 +++++++++ 17 files changed, 898 insertions(+), 5 deletions(-) create mode 100644 apps/ttkode/README.md create mode 100644 apps/ttkode/pyproject.toml create mode 100755 apps/ttkode/ttkode/__init__.py create mode 100644 apps/ttkode/ttkode/__main__.py create mode 100644 apps/ttkode/ttkode/app/__init__.py create mode 100644 apps/ttkode/ttkode/app/about.py create mode 100644 apps/ttkode/ttkode/app/cfg.py create mode 100644 apps/ttkode/ttkode/app/kodeformatter.py create mode 100644 apps/ttkode/ttkode/app/kodetextdocument.py create mode 100644 apps/ttkode/ttkode/app/kodetextedit.py create mode 100644 apps/ttkode/ttkode/app/main.py create mode 100644 apps/ttkode/ttkode/app/options.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af51f9e6..5107d587 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,13 +70,16 @@ jobs: _VERSION_TTK=$(jq -r '.["libs/pyTermTk" ]' .release-please-manifest.json) _VERSION_DPT=$(jq -r '.["apps/dumbPaintTool"]' .release-please-manifest.json) _VERSION_T_D=$(jq -r '.["apps/ttkDesigner" ]' .release-please-manifest.json) + _VERSION_KOD=$(jq -r '.["apps/ttkode" ]' .release-please-manifest.json) _NAME_TTK=$(jq -r '.packages["libs/pyTermTk" ]["package-name"]' .release-please-config.json) _NAME_DPT=$(jq -r '.packages["apps/dumbPaintTool"]["package-name"]' .release-please-config.json) _NAME_T_D=$(jq -r '.packages["apps/ttkDesigner" ]["package-name"]' .release-please-config.json) + _NAME_KOD=$(jq -r '.packages["apps/ttkode" ]["package-name"]' .release-please-config.json) echo "Version ${_NAME_TTK}: ${_VERSION_TTK}" echo "Version ${_NAME_DPT}: ${_VERSION_DPT}" echo "Version ${_NAME_T_D}: ${_VERSION_T_D}" + echo "Version ${_NAME_KOD}: ${_VERSION_KOD}" echo '::endgroup::' echo '::group::Update the Versions' @@ -96,20 +99,23 @@ jobs: apps/dumbPaintTool/dumbPaintTool/__init__.py sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkDesigner/pyproject.toml fi - + if grep -q "${_NAME_KOD}: ${_VERSION_KOD}" <<< ' ${{ steps.release-please.outputs.pr }}': then + sed -i \ + "s|__version__:str.*|__version__:str = '${_VERSION_KOD}'|" \ + apps/ttkode/ttkode/__init__.py + sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkode/pyproject.toml + fi cp libs/pyTermTk/CHANGELOG.md CHANGELOG.md echo '::endgroup::' echo '::group::Push the Versions' git add \ - libs/pyTermTk/TermTk/TTkCore/cfg.py \ - apps/dumbPaintTool/dumbPaintTool/__init__.py \ - apps/ttkDesigner/ttkDesigner/__init__.py \ + apps/*/*/__init__.py \ libs/pyTermTk/TermTk/__init__.py \ CHANGELOG.md find . -name pyproject.toml xargs git add - git commit -m "chore: updated TermTk and apps to versions to ${_VERSION_TTK}, ${_VERSION_DPT}, ${_VERSION_T_D}" + git commit -m "chore: updated TermTk and apps to versions to ${_VERSION_TTK}, ${_VERSION_DPT}, ${_VERSION_T_D} ${_VERSION_KOD}" git push echo '::endgroup::' @@ -214,4 +220,15 @@ jobs: pkg_folder: apps/dumbPaintTool needs: - release-please + secrets: inherit + + publish-ttkode: + if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/ttkode--release_created'] }} + name: Publish ttkode + uses: ./.github/workflows/python-publish.yml + with: + pkg_name: ttkode + pkg_folder: apps/ttkode + needs: + - release-please secrets: inherit \ No newline at end of file diff --git a/.release-please-config.json b/.release-please-config.json index 849c429d..fe9c3f27 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -23,6 +23,9 @@ }, "apps/ttkDesigner": { "package-name": "ttkDesigner" + }, + "apps/ttkode": { + "package-name": "ttkode" } } } diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8a334c96..368979e4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,5 +1,6 @@ { "libs/pyTermTk": "0.41.17-a.0", + "apps/ttkode": "0.2.14-a.2", "apps/ttkDesigner": "0.41.4-a.54", "apps/dumbPaintTool": "0.41.8-a.54" } diff --git a/.vscode/launch.json b/.vscode/launch.json index 0a74864e..7d9887e7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -105,6 +105,17 @@ "experiments/untitled.DPT.json" ] }, + { + "name": "py Debug: Module ttkode", + "type": "debugpy", + "request": "launch", + "module": "ttkode", + "console": "integratedTerminal", + "justMyCode": true, + "env": { + "PYTHONPATH": "./apps/ttkode" + } + }, { "name": "Python: Demo", "type": "debugpy", diff --git a/README.md b/README.md index 3a6fc0bf..9bb98f32 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ Be inspired by [the Tutorials](https://github.com/ceccopierangiolieugenio/pyTerm ## [Api Definitions](https://ceccopierangiolieugenio.github.io/pyTermTk-Docs/index.html#api-reference) Don't get bored by the [Api Definitions](https://ceccopierangiolieugenio.github.io/pyTermTk-Docs/index.html#api-reference) +## [ttkode](https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkode) +Burn your fingers with the Terminal Studio Kode + ## [ttkDesigner](https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkDesigner) Smell deliciousness with the official [pyTermTk](https://github.com/ceccopierangiolieugenio/pyTermTk) tool for designing and building Text-based user interfaces ([TUI](https://en.wikipedia.org/wiki/Text-based_user_interface)s) diff --git a/apps/ttkode/README.md b/apps/ttkode/README.md new file mode 100644 index 00000000..64e8cab7 --- /dev/null +++ b/apps/ttkode/README.md @@ -0,0 +1,40 @@ +![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) +![Usage](https://img.shields.io/badge/Usage-Terminal%20User%20Interface-yellow) +![Python](https://img.shields.io/badge/Python-v3.8%5E-green?logo=python) +![ttkode_version](https://img.shields.io/github/v/tag/ceccopierangiolieugenio/ttkode?label=version) +[![pypi_version](https://img.shields.io/pypi/v/ttkode?label=pypi)](https://pypi.org/project/ttkode) +[![pypi_version](https://img.shields.io/twitter/follow/Pier95886803?style=social&logo=twitter)](https://twitter.com/hashtag/pyTermTk?src=hashtag_click&f=live) + +# ttkode +TerminalToolKit (Studio) Code (editor) + +A hopefully fast and mesmerizingly advanced [text-based](https://en.wikipedia.org/wiki/Text-based_user_interface) code editor inspired by [vscode](https://code.visualstudio.com) + +## Features (TBD) +- Search Panel +- Highlight +- Bookmarks +- Shiny ASCII Red Peppers + +[![screenshot](https://raw.githubusercontent.com/ceccopierangiolieugenio/binaryRepo/master/TTKode/ttkode.0.0.0.gif)](https://pypi.org/project/tlogg) + +[Peek 2022-10-08 22-25.webm](https://user-images.githubusercontent.com/8876552/195099208-65d4707e-0340-4077-835a-87ae6c8ae3b6.webm) + +# Install from [pypi](https://pypi.org/project/ttkode) +```bash +pip install ttkode +``` +# QuickRun +```bash + $ ttkode -h +usage: ttkode [-h] [-c C] path [path ...] + +positional arguments: + path the dir/filename/s + +optional arguments: + -h, --help show this help message and exit + -c C config folder (default: "/home/user/.config/ttkode") +``` + + diff --git a/apps/ttkode/pyproject.toml b/apps/ttkode/pyproject.toml new file mode 100644 index 00000000..33116666 --- /dev/null +++ b/apps/ttkode/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "ttkode" +dynamic = ["version"] +readme = {file = "README.md", content-type = "text/markdown"} +authors = [ + {name = "Eugenio Parodi", email = "ceccopierangiolieugenio@googlemail.com"}, +] +description = "Terminal ToolKit Studio Code editor" +requires-python = ">=3.9" +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Topic :: Terminals", + "Topic :: Software Development :: User Interfaces", +] +dependencies = [ + 'pyTermTk>=0.41.17-a.0', + 'appdirs', + 'copykitten', + 'pygments' +] + +[project.urls] +Homepage = "https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkode" +Repository = "https://github.com/ceccopierangiolieugenio/pyTermTk.git" +Issues = "https://github.com/ceccopierangiolieugenio/pyTermTk/issues" +Changelog = "https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/apps/ttkDesigner/CHANGELOG.md" + +[project.scripts] +ttkode = "ttkode:main" + +[tool.setuptools] +packages = ["ttkode", "ttkode.app"] + +[tool.setuptools.dynamic] +version = {attr = "ttkode.__version__"} diff --git a/apps/ttkode/ttkode/__init__.py b/apps/ttkode/ttkode/__init__.py new file mode 100755 index 00000000..439b5d66 --- /dev/null +++ b/apps/ttkode/ttkode/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +__version__:str = '0.2.4-a.15' + +from .app import * + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/apps/ttkode/ttkode/__main__.py b/apps/ttkode/ttkode/__main__.py new file mode 100644 index 00000000..22bab16d --- /dev/null +++ b/apps/ttkode/ttkode/__main__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .app import main + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/apps/ttkode/ttkode/app/__init__.py b/apps/ttkode/ttkode/app/__init__.py new file mode 100644 index 00000000..3f68dd5f --- /dev/null +++ b/apps/ttkode/ttkode/app/__init__.py @@ -0,0 +1,29 @@ + +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .cfg import * +# from .glbl import * +from .main import * +from .kodetextdocument import KodeTextDocument \ No newline at end of file diff --git a/apps/ttkode/ttkode/app/about.py b/apps/ttkode/ttkode/app/about.py new file mode 100644 index 00000000..8e86da69 --- /dev/null +++ b/apps/ttkode/ttkode/app/about.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.color import TTkColor +from TermTk import TTkAbout, TTkWindow +from .cfg import TTKodeCfg + +class About(TTkAbout): + ttkode = [ + "__________________ __ ", + "\_______________ / / / ┌─┐ ", + " /\ /\ | |__/ /___ __| |_____ ", + " | | | | | _ // _ \ / _ | ___ |", + " | | | | | | \ \ |_| ( (_| | ____|", + " | | | | └─┘ \_)___/ \____|_____)", + " | | | | ", + " └──┘ └──┘ ",] + + __slots__=('_image') + def __init__(self, *args, **kwargs): + TTkAbout.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'About' ) + self.setTitle('[PierCecco Cecco] Eugenio Parodi proudly presents...') + self.resize(56,16) + + def paintEvent(self, canvas): + c = [0xFF,0xFF,0xFF] + for y, line in enumerate(About.ttkode): + canvas.drawText(pos=(13,3+y),text=line, color=TTkColor.fg(f'#{c[0]:02X}{c[1]:02X}{c[2]:02X}')) + c[2]-=0x18 + c[0]-=0x08 + canvas.drawText(pos=(26, 9),text=f" Version: {TTKodeCfg.version}", color=TTkColor.fg('#AAAAFF')) + canvas.drawText(pos=(14,11),text=f"Powered By, pyTermTk") + canvas.drawText(pos=( 2,13),text=f"https://github.com/ceccopierangiolieugenio/ttkode", color=TTkColor.fg('#44FFFF')) + canvas.drawText(pos=( 2,14),text=f"https://github.com/ceccopierangiolieugenio/pyTermTk", color=TTkColor.fg('#44FFFF')) + + TTkWindow.paintEvent(self, canvas) diff --git a/apps/ttkode/ttkode/app/cfg.py b/apps/ttkode/ttkode/app/cfg.py new file mode 100644 index 00000000..f2c1ab3d --- /dev/null +++ b/apps/ttkode/ttkode/app/cfg.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import json + +class TTKodeCfg: + version="__VERSION__" + name="__NAME__" + cfgVersion = '1.0' + pathCfg="." + options={} + maxsearches=200 + + @staticmethod + def save(searches=True, filters=True, colors=True, options=True): + os.makedirs(TTKodeCfg.pathCfg, exist_ok=True) + optionsPath = os.path.join(TTKodeCfg.pathCfg,'options.json') + + def writeCfg(path, cfg): + fullCfg = { + 'version':TTKodeCfg.cfgVersion, + 'cfg':cfg } + # with open(path, 'w') as f: + # json.dump(fullCfg, f, sort_keys=False, default_flow_style=False) + + if options: writeCfg(optionsPath, TTKodeCfg.options) + + @staticmethod + def load(): + optionsPath = os.path.join(TTKodeCfg.pathCfg,'options.json') + + # if os.path.exists(optionsPath): + # with open(optionsPath) as f: + # TTKodeCfg.options = json.load(f, Loader=json.SafeLoader)['cfg'] diff --git a/apps/ttkode/ttkode/app/kodeformatter.py b/apps/ttkode/ttkode/app/kodeformatter.py new file mode 100644 index 00000000..dbd9a4de --- /dev/null +++ b/apps/ttkode/ttkode/app/kodeformatter.py @@ -0,0 +1,142 @@ +# MIT License +# +# Copyright (c) 2022 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from pygments.formatter import Formatter +from pygments.token import Keyword, Name, Comment, String, Error, \ + Number, Operator, Generic, Token, Whitespace + +from TermTk import TTkString, TTkColor, TTkLog + +#: Map token types to a tuple of color values for light and dark +#: backgrounds. +TTKODE_COLORS = { + Token: TTkColor.RST, # ('', ''), + + Whitespace: TTkColor.fg('#888888') , # ('gray', 'brightblack'), + Comment: TTkColor.fg('#888888') , # ('gray', 'brightblack'), + Comment.Preproc: TTkColor.fg('#00FFFF') , # ('cyan', 'brightcyan'), + Keyword: TTkColor.fg('#0000FF') , # ('blue', 'brightblue'), + Keyword.Type: TTkColor.fg('#00FFFF') , # ('cyan', 'brightcyan'), + Operator.Word: TTkColor.fg('#FF8800') , # ('magenta', 'brightmagenta'), + Name.Builtin: TTkColor.fg('#00FFFF') , # ('cyan', 'brightcyan'), + Name.Function: TTkColor.fg('#00FF00') , # ('green', 'brightgreen'), + Name.Namespace: TTkColor.fg('#00FFFF') , # ('_cyan_', '_brightcyan_'), + Name.Class: TTkColor.fg('#00FF00') , # ('_green_', '_brightgreen_'), + Name.Exception: TTkColor.fg('#00FFFF') , # ('cyan', 'brightcyan'), + Name.Decorator: TTkColor.fg('#888888') , # ('brightblack', 'gray'), + Name.Variable: TTkColor.fg('#888888') , # ('red', 'brightred'), + Name.Constant: TTkColor.fg('#888888') , # ('red', 'brightred'), + Name.Attribute: TTkColor.fg('#00FFFF') , # ('cyan', 'brightcyan'), + Name.Tag: TTkColor.fg('#0000FF') , # ('brightblue', 'brightblue'), + String: TTkColor.fg('#FFFF00') , # ('yellow', 'yellow'), + Number: TTkColor.fg('#0000FF') , # ('blue', 'brightblue'), + + Generic.Deleted: TTkColor.fg('#FF0000') , # ('brightred', 'brightred'), + Generic.Inserted: TTkColor.fg('#00FF00') , # ('green', 'brightgreen'), + Generic.Heading: TTkColor.fg('#888888') , # ('**', '**'), + Generic.Subheading: TTkColor.fg('#FF8800') , # ('*magenta*', '*brightmagenta*'), + Generic.Prompt: TTkColor.fg('#888888') , # ('**', '**'), + Generic.Error: TTkColor.fg('#FF0000') , # ('brightred', 'brightred'), + + Error: TTkColor.fg('#FF0000') , # ('_brightred_', '_brightred_'), +} + +class KodeFormatter(Formatter): + class Data(): + __slots__=('lines', 'block', 'error', 'multiline') + def __init__(self, lines, block): + self.lines = lines + self.block = block + self.error = None + self.multiline = False + + __slots__ = ('_dl', '_blockNum', '_kodeStyles') + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._kodeStyles = {} + self._blockNum = 1 + for token, style in self.style: + # Token = Token.Comment.PreprocFile + # style = { + # 'color': '6272a4', + # 'bgcolor': None, + # 'bold': False, 'italic': False, 'underline': False, + # 'border': None, + # 'roman': None, 'sans': None, 'mono': None, + # 'ansicolor': None, 'bgansicolor': None} + + # TTkLog.debug(f"{token=} {style=}") + color = TTkColor.RST + if style['color']: + color += TTkColor.fg(f"#{style['color']}") + if style['bgcolor']: + color += TTkColor.bg(f"#{style['bgcolor']}") + if style['bold']: + color += TTkColor.BOLD + if style['italic']: + color += TTkColor.ITALIC + if style['underline']: + color += TTkColor.UNDERLINE + self._kodeStyles[token] = color + + super().__init__() + + def setDl(self,dl): + self._dl = dl + + def format(self, tokensource, _): + multiline = False + multilineId = 0 + for ttype, value in tokensource: + if ttype == Error and self._dl.error is None: + self._dl.error = len(self._dl.lines)-1 + # self._dl.multiline = ttype == Comment.Multiline + multiline = ttype == Comment.Multiline + + while ttype not in self._kodeStyles: + ttype = ttype.parent + # TTkLog.debug (f"{ttype=}") + # TTkLog.debug (f"{value=}") + color = self._kodeStyles[ttype] + + values = value.split('\n') + + self._dl.lines[-1] += TTkString(values[0],color) + self._dl.lines += [TTkString(t,color) for t in values[1:]] + self._dl.block[-1] = self._blockNum + self._dl.block += [self._blockNum]*(len(values)-1) + + # self._dl.lines += [TTkString(t) for t in value.split('\n')] + + # multiline = len(values)>1 if self._dl.lines[-1]._text == values[-1] else self._dl.multiline + # if self._dl.lines[-1]._text == '' or not multiline: + # self._blockNum += 1 + # multilineId = len(self._dl.lines) + + if multiline: + multilineId += len(values) + else: + multilineId = 0 + self._blockNum += 1 + + if multiline: + self._dl.multiline = multilineId diff --git a/apps/ttkode/ttkode/app/kodetextdocument.py b/apps/ttkode/ttkode/app/kodetextdocument.py new file mode 100644 index 00000000..9c6f4e1b --- /dev/null +++ b/apps/ttkode/ttkode/app/kodetextdocument.py @@ -0,0 +1,165 @@ +# MIT License +# +# Copyright (c) 2022 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from threading import Lock + +from pygments import highlight +from pygments.util import ClassNotFound +from pygments.lexers import guess_lexer, guess_lexer_for_filename, special +from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter + +from TermTk import TTk, TTkK, TTkLog, TTkCfg, TTkTheme, TTkTerm, TTkHelper, TTkTimer +from TermTk import TTkString +from TermTk import TTkColor, TTkColorGradient +from TermTk import pyTTkSlot, pyTTkSignal + +from TermTk import TTkTextDocument +from .kodeformatter import KodeFormatter + +class KodeTextDocument(TTkTextDocument): + _linesRefreshed = 30 + __slots__ = ( + '_filePath', '_timerRefresh', + 'kodeHighlightUpdate', '_kodeDocMutex', + '_blocks', '_changedContent', '_refreshContent', + '_lexer', '_formatter') + def __init__(self, filePath:str="", **kwargs): + self.kodeHighlightUpdate = pyTTkSignal() + self._kodeDocMutex = Lock() + self._lexer = None + self._blocks = [] + # self._formatter = KodeFormatter(style='dracula') + self._filePath = filePath + self._formatter = KodeFormatter(style='gruvbox-dark') + self._timerRefresh = TTkTimer() + super().__init__(**kwargs) + self._changedContent = (0,0,len(self._dataLines)) + self._refreshContent = (0,KodeTextDocument._linesRefreshed) + self._timerRefresh.timeout.connect(self._refreshEvent) + self._timerRefresh.start(0.3) + self.contentsChange.connect(lambda a,b,c: TTkLog.debug(f"{a=} {b=} {c=}")) + self.contentsChange.connect(self._saveChangedContent) + + @pyTTkSlot(int,int,int) + def _saveChangedContent(self,a,b,c): + if self._changedContent: + self._changedContent = TTkTextDocument._mergeChangesSlices(self._changedContent,(a,b,c)) + else: + self._changedContent = (a,b,c) + if not self._refreshContent: + self._refreshContent = (self._changedContent[0], KodeTextDocument._linesRefreshed) + self._timerRefresh.start(0.1) + + @pyTTkSlot() + def _refreshEvent(self): + if not self._refreshContent: return + self._kodeDocMutex.acquire() + + ra,rb = self._refreshContent + + if self._changedContent: + ca,cb,cc = self._changedContent + self._changedContent = None + self._blocks[ca:ca+cb] = [0]*cc + ra = min(ra,ca) + + # find the beginning of the current block + # TTkLog.debug(self._blocks) + if ra and self._blocks: + blockId = self._blocks[ra] + for i,v in enumerate(reversed(self._blocks[:ra])): + # TTkLog.debug(f"{i=}:{v=} {blockId=}") + if v == blockId or not blockId: + blockId = v + ra -= 1 + rb += 1 + else: + break + + # TTkLog.debug(f"{ra=} {rb=}") + + eof = False + if (ra+rb) >= len(self._dataLines): + rb = len(self._dataLines)-ra + eof=True + + tsl = self._dataLines[ra:ra+rb] + # Find the offset from the first not empty line + # because pygments autostrip the heading empty lines + offset = 0 + for i,l in enumerate(tsl): + if l != '': + offset = i + break + + rawl = [l._text for l in tsl[offset:]] + rawt = '\n'.join(rawl) + if not self._lexer: + try: + self._lexer = guess_lexer_for_filename(self._filePath, rawt) + except ClassNotFound: + self._lexer = special.TextLexer() + + # TTkLog.debug(f"Refresh {self._lexer.name} {ra=} {rb=}") + tsl1 = [TTkString()]*(offset+1) + block = [0]*(offset+1) + + kfd = KodeFormatter.Data(tsl1, block) + self._formatter.setDl(kfd) + + highlight(rawt, self._lexer, self._formatter) + + # for ll in tsl: + # TTkLog.debug(f"1: -{ll}-") + # for ll in tsl1: + # TTkLog.debug(f"2: -{ll}-") + + tsl1 = tsl1[:rb] + block = block[:rb] + self._dataLines[ra:ra+rb] = tsl1 + tsl[len(tsl1):] + self._blocks[ra:ra+rb] = block + [-1]*(rb-len(block)) + # TTkLog.debug(self._blocks) + + if kfd.error is not None: + self._refreshContent = (ra+kfd.error,rb<<1) + # TTkLog.debug(f"Error: {self._refreshContent=}") + elif kfd.multiline is not None: + self._refreshContent = (ra+kfd.multiline,rb<<1) + elif (ra+rb) < len(self._dataLines): + self._refreshContent = (ra+rb,KodeTextDocument._linesRefreshed) + else: + self._refreshContent = None + # TTkLog.debug(f"{self._refreshContent=}") + + if not eof: + self._timerRefresh.start(0.03) + else: + TTkLog.debug(f"Refresh {self._lexer.name} DONE!!!") + + self._kodeDocMutex.release() + self.kodeHighlightUpdate.emit() + + def getLock(self): + return self._kodeDocMutex + + def filePath(self): + return self._filePath \ No newline at end of file diff --git a/apps/ttkode/ttkode/app/kodetextedit.py b/apps/ttkode/ttkode/app/kodetextedit.py new file mode 100644 index 00000000..36d2c5a5 --- /dev/null +++ b/apps/ttkode/ttkode/app/kodetextedit.py @@ -0,0 +1,31 @@ +# MIT License +# +# Copyright (c) 2022 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from TermTk import TTkLog +from TermTk import TTkTextEditView + +class KodeTextEditView(TTkTextEditView): + def keyEvent(self, evt) -> bool: + self.document().getLock().acquire() + ret = super().keyEvent(evt) + self.document().getLock().release() + return ret \ No newline at end of file diff --git a/apps/ttkode/ttkode/app/main.py b/apps/ttkode/ttkode/app/main.py new file mode 100644 index 00000000..c1db61b2 --- /dev/null +++ b/apps/ttkode/ttkode/app/main.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import re +import sys +import argparse + +import appdirs + +from pygments import highlight +from pygments.lexers import PythonLexer +from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter + +from TermTk import TTk, TTkK, TTkLog, TTkCfg, TTkColor, TTkTheme, TTkTerm, TTkHelper +from TermTk import TTkString +from TermTk import TTkColorGradient +from TermTk import pyTTkSlot, pyTTkSignal + +from TermTk import TTkFrame, TTkButton +from TermTk import TTkTabWidget, TTkKodeTab +from TermTk import TTkAbstractScrollArea, TTkAbstractScrollView +from TermTk import TTkFileDialogPicker +from TermTk import TTkFileTree, TTkTextEdit + +from TermTk import TTkGridLayout +from TermTk import TTkSplitter + +from .cfg import * +from .about import * +# from .options import optionsFormLayout, optionsLoadTheme +from .kodetextedit import KodeTextEditView +from .kodetextdocument import KodeTextDocument + +class TTKode(TTkGridLayout): + __slots__ = ('_kodeTab', '_documents') + def __init__(self, *, files, **kwargs): + self._documents = {} + + super().__init__(**kwargs) + + self.addWidget(splitter := TTkSplitter()) + + layoutLeft = TTkGridLayout() + splitter.addItem(layoutLeft, 20) + + hSplitter = TTkSplitter(parent=splitter, orientation=TTkK.HORIZONTAL) + + menuFrame = TTkFrame(border=False, maxHeight=1) + + self._kodeTab = TTkKodeTab(parent=hSplitter, border=False, closable=True) + + fileMenu = menuFrame.newMenubarTop().addMenu("&File") + fileMenu.addMenu("Open").menuButtonClicked.connect(self._showFileDialog) + fileMenu.addMenu("Close") # .menuButtonClicked.connect(self._closeFile) + fileMenu.addMenu("Exit").menuButtonClicked.connect(lambda _:TTkHelper.quit()) + + def _showAbout(btn): + TTkHelper.overlay(None, About(), 30,10) + def _showAboutTTk(btn): + TTkHelper.overlay(None, TTkAbout(), 30,10) + + helpMenu = menuFrame.newMenubarTop().addMenu("&Help", alignment=TTkK.RIGHT_ALIGN) + helpMenu.addMenu("About ...").menuButtonClicked.connect(_showAbout) + helpMenu.addMenu("About ttk").menuButtonClicked.connect(_showAboutTTk) + + fileTree = TTkFileTree(path='.') + + layoutLeft.addWidget(menuFrame, 0,0) + layoutLeft.addWidget(fileTree, 1,0) + layoutLeft.addWidget(quitbtn := TTkButton(border=True, text="Quit", maxHeight=3), 2,0) + + quitbtn.clicked.connect(TTkHelper.quit) + + for file in files: + self._openFile(file) + + fileTree.fileActivated.connect(lambda x: self._openFile(x.path())) + + pyTTkSlot() + def _showFileDialog(self): + filePicker = TTkFileDialogPicker(pos = (3,3), size=(75,24), caption="Pick Something", path=".", fileMode=TTkK.FileMode.AnyFile ,filter="All Files (*);;Python Files (*.py);;Bash scripts (*.sh);;Markdown Files (*.md)") + filePicker.pathPicked.connect(self._openFile) + TTkHelper.overlay(None, filePicker, 20, 5, True) + + def _openFile(self, filePath): + filePath = os.path.realpath(filePath) + if filePath in self._documents: + doc = self._documents[filePath]['doc'] + else: + with open(filePath, 'r') as f: + content = f.read() + doc = KodeTextDocument(text=content, filePath=filePath) + self._documents[filePath] = {'doc':doc,'tabs':[]} + tview = KodeTextEditView(document=doc, readOnly=False) + tedit = TTkTextEdit(textEditView=tview, lineNumber=True) + doc.kodeHighlightUpdate.connect(tedit.update) + label = TTkString(TTkCfg.theme.fileIcon.getIcon(filePath),TTkCfg.theme.fileIconColor) + TTkColor.RST + " " + os.path.basename(filePath) + + self._kodeTab.addTab(tedit, label) + self._kodeTab.setCurrentWidget(tedit) + + # def _closeFile(): + # if (index := KodeTab.lastUsed.currentIndex()) >= 0: + # KodeTab.lastUsed.removeTab(index) + +def main(): + TTKodeCfg.pathCfg = appdirs.user_config_dir("ttkode") + + parser = argparse.ArgumentParser() + # parser.add_argument('-f', help='Full Screen', action='store_true') + parser.add_argument('-c', help=f'config folder (default: "{TTKodeCfg.pathCfg}")', default=TTKodeCfg.pathCfg) + parser.add_argument('filename', type=str, nargs='*', + help='the filename/s') + args = parser.parse_args() + + # TTkLog.use_default_file_logging() + + TTKodeCfg.pathCfg = args.c + TTkLog.debug(f"Config Path: {TTKodeCfg.pathCfg}") + + TTKodeCfg.load() + + # if 'theme' not in TTKodeCfg.options: + # TTKodeCfg.options['theme'] = 'NERD' + # optionsLoadTheme(TTKodeCfg.options['theme']) + + TTkTheme.loadTheme(TTkTheme.NERD) + + root = TTk( layout=TTKode(files=args.filename), title="TTkode", + sigmask=( + # TTkTerm.Sigmask.CTRL_C | + TTkTerm.Sigmask.CTRL_Q | + TTkTerm.Sigmask.CTRL_S | + TTkTerm.Sigmask.CTRL_Z )) + + root.mainloop() diff --git a/apps/ttkode/ttkode/app/options.py b/apps/ttkode/ttkode/app/options.py new file mode 100644 index 00000000..7165b046 --- /dev/null +++ b/apps/ttkode/ttkode/app/options.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import copy + +from . import TTKodeCfg, TloggGlbl + +from TermTk import * + +def optionsLoadTheme(theme): + if theme == 'ASCII': + TTkTheme.loadTheme(TTkTheme.ASCII) + elif theme == 'UTF8': + TTkTheme.loadTheme(TTkTheme.UTF8) + elif theme == 'NERD': + TTkTheme.loadTheme(TTkTheme.NERD) + +def optionsFormLayout(win): + options = copy.deepcopy(TTKodeCfg.options) + + retLayout = TTkGridLayout() + bottomLayout = TTkGridLayout() + + themesFrame = TTkFrame(title="Theme", border=True, layout=TTkVBoxLayout(), maxHeight=5, minHeight=5) + # Themes + themesFrame.layout().addWidget(r1 := TTkRadioButton(text="ASCII", name="theme", checked=options['theme'] == 'ASCII')) + themesFrame.layout().addWidget(r2 := TTkRadioButton(text="UTF-8", name="theme", checked=options['theme'] == 'UTF8')) + themesFrame.layout().addWidget(r3 := TTkRadioButton(text="Nerd", name="theme", checked=options['theme'] == 'NERD')) + + retLayout.addWidget(themesFrame,0,0) + retLayout.addWidget(TTkSpacer() ,1,0,1,2) + + retLayout.addItem(bottomLayout ,2,0,1,2) + bottomLayout.addWidget(applyBtn := TTkButton(text="Apply", border=True, maxHeight=3),0,1) + bottomLayout.addWidget(cancelBtn := TTkButton(text="Cancel", border=True, maxHeight=3),0,2) + bottomLayout.addWidget(okBtn := TTkButton(text="OK", border=True, maxHeight=3),0,3) + + def _saveOptions(): + if r1.checkState() == TTkK.Checked: options['theme'] = 'ASCII' + if r2.checkState() == TTkK.Checked: options['theme'] = 'UTF8' + if r3.checkState() == TTkK.Checked: options['theme'] = 'NERD' + TTKodeCfg.options = options + TTKodeCfg.save(searches=False, filters=False, colors=False, options=True) + optionsLoadTheme(options['theme']) + TloggGlbl.refreshViews() + TTkHelper.updateAll() + + applyBtn.clicked.connect(_saveOptions) + okBtn.clicked.connect(_saveOptions) + okBtn.clicked.connect(win.close) + cancelBtn.clicked.connect(win.close) + + return retLayout