Compare commits

..

15 Commits

Author SHA1 Message Date
d4b962769f install isort and black for code formatting 2025-05-16 08:40:43 +02:00
d9c362419b handle tabs correctly when highlighting text
With the old solution I replaced tabs with four spaces and then did
some calculations to get the correct byte positions for a coordinate.
With the new solution (using FontMetric.horizontalAdvance) this is not
necessary anymore and we can use tha native tab.
But I cannot change the tab width. It is always 8 characters.
You can set tabStopDistance on horizontalAdvance(), but not on
elided_text() and I did not find a way to use it while rendering text.
2025-05-06 20:45:19 +02:00
bfd8ce841f add .envrc so that direnv can set the venv dir to the path 2025-05-06 20:17:10 +02:00
a41e5b79a3 add 'about qt' dialog 2025-05-06 20:14:59 +02:00
75578b6126 rename venv 2025-04-06 19:02:46 +02:00
9afc4d1d9c fix exception when selecting text by clicking behind the last character 2025-04-06 19:02:33 +02:00
d36724f3e7 replace watchdog with active polling thread
watchdog does not work on windows. For some
reason file modification events are not
emitted.
Fixed by replacing watchdog with a thread
that polls the modification date ever 0.5s.

Also fixed a bug that the hit view was not
properly destructed.
2025-04-06 09:34:08 +02:00
bcd525d787 ignore version file 2025-04-05 10:46:38 +02:00
8289042af4 remove version file from repository
I don't want to have to commit an ever changing version file.
2025-04-05 10:46:00 +02:00
0246a3fb19 only add versions on Windows
The version-file property only has any effect on Windows.
2025-04-05 10:42:59 +02:00
617c7f161f Update README.md 2025-03-26 10:31:37 +00:00
67f16571f1 add version info to compiled executable
This works on Windows, but it might not work on Linux.
Also the version number is not updated automatically.
I probably have to generate the file from a template, because
even though this is a python file you cannot add python code.
2025-03-26 11:17:07 +01:00
be5e0c9ae6 update instructions.md for python 3.12 2025-03-25 18:35:20 +01:00
51c02b3d55 add missing file selectionPos
forgot to add it in one of the previous commits
2025-03-25 17:04:37 +01:00
c19cdf6f41 feature: add changelog to about dialog 2025-03-24 20:36:18 +01:00
21 changed files with 232 additions and 82 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
PATH=$(pwd)/venv312/bin:$PATH

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ dist
testbed testbed
icons-not-used icons-not-used
venv* venv*
*.spec *.spec
/version.txt

1
.idea/misc.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Black"> <component name="Black">
<option name="enabledOnSave" value="true" />
<option name="sdkName" value="Python 3.12 (krowlog)" /> <option name="sdkName" value="Python 3.12 (krowlog)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (krowlog)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (krowlog)" project-jdk-type="Python SDK" />

View File

@@ -17,7 +17,7 @@ KrowLog is a viewer for text files of arbitrary size.
* Select arbitrary strings (not just full lines). * Select arbitrary strings (not just full lines).
* Double click selects word. * Double click selects word.
* Triple click selects line. * Triple click selects line.
* Copy protection: Users is warned before creating a clipboard more than 5 MB in size. They can choose to copy the * Copy protection: Users are warned before creating a clipboard more than 5 MB in size. They can choose to copy the
selection into a new file instead. selection into a new file instead.
* Optionally open a new tab when saving selection as file. * Optionally open a new tab when saving selection as file.
* Panel for temporary notes * Panel for temporary notes

16
changelog.txt Normal file
View File

@@ -0,0 +1,16 @@
Next Version
* Feature: Add changelog.
* Fix: When the range sliders are overlapping the end slider cannot be moved.
* Feature: Get version number from git tags.
* Feature: You can now "follow" a file. When enabled the file is automatically reloaded
and scrolled to the end. Any manual scroll action disables "follow" mode.
* Feature: Better support for fonts and characters with non-uniform width.
* Fix: Cannot scroll to arbitrary positions in a file if the file is larger than 2GB
* Feature: File type specific highlighters.
0.2.1
* Feature: Show how many bytes are selected.
* Feature: Highlighters can be disabled.
* Feature: If a regex contains a group, then only the group is highlighted.
Using a filter expression like '(\d+)ms' will only hightlight the number.

View File

@@ -1,6 +1,8 @@
import os import os
krow_icon = "icons" + os.sep + "krowlog.svg" krow_icon = "icons" + os.sep + "krowlog.svg"
qt_icon = "icons" + os.sep + "qt-logo.png"
license_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "LICENSE" license_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "LICENSE"
changelog_file = os.path.dirname(os.path.realpath(__file__)) + os.sep + "changelog.txt"
tab_width = 4 tab_width = 4

View File

@@ -8,12 +8,12 @@ Just run `make_installer.py` with the following command. The distribution can be
to run this on all target platforms. to run this on all target platforms.
``` ```
venv311/bin/python make_installer.py venv312/bin/python make_installer.py
``` ```
## Update Python ## Update Python
1. install the latest python version. We need the dev version, because PyInstaller requires it. 1. install the latest python version. We need the dev version, because PyInstaller requires it.
`sudo apt install python-3.11-dev python-3.11-venv` `sudo apt install python-3.12-dev python-3.12-venv`
2. create new virtual environment with `python3.11 -m venv /path/to/venv` 2. create new virtual environment with `python3.12 -m venv /path/to/venv`
3. select the venv in PyCharm 3. select the venv in PyCharm

View File

@@ -1,21 +1,26 @@
import argparse import argparse
import gettext
import logging import logging
import os
import signal import signal
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
import sys import sys
from pathlib import Path
from PySide6 import QtCore
from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication
import constants import constants
from src import install from src import install
from src.pluginregistry import PluginRegistry from src.pluginregistry import PluginRegistry
import gettext
from src.ui.icon import Icon from src.ui.icon import Icon
from pathlib import Path
import os
__version__ = Path(os.path.dirname(os.path.realpath(__file__)) + os.sep + "version.txt").read_text() version_file = Path(
os.path.dirname(os.path.realpath(__file__)) + os.sep + "version.txt"
)
__version__ = version_file.read_text() if version_file.is_file() else "0.0.0"
gettext.install('krowlog', 'locale') gettext.install("krowlog", "locale")
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
log = logging.getLogger("main") log = logging.getLogger("main")
@@ -27,7 +32,7 @@ def register_signal_handler():
def stop_signal(signum, _stackframe): def stop_signal(signum, _stackframe):
""" Handle terminate signal """ """Handle terminate signal"""
try: try:
log.info("Terminate signal received. %s", signum) log.info("Terminate signal received. %s", signum)
QtCore.QCoreApplication.quit() QtCore.QCoreApplication.quit()
@@ -67,7 +72,9 @@ class CmdArgs:
def parse_command_line_parameters() -> CmdArgs: def parse_command_line_parameters() -> CmdArgs:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('files', metavar='F', type=str, nargs='*', help='file(s) to open') parser.add_argument(
"files", metavar="F", type=str, nargs="*", help="file(s) to open"
)
namespace = parser.parse_args() namespace = parser.parse_args()
return CmdArgs(files=namespace.files) return CmdArgs(files=namespace.files)
@@ -76,7 +83,9 @@ if __name__ == "__main__":
cmd_args = parse_command_line_parameters() cmd_args = parse_command_line_parameters()
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setWindowIcon(Icon(constants.krow_icon)) # works only for Linux (but only X11, not Wayland) app.setWindowIcon(
Icon(constants.krow_icon)
) # works only for Linux (but only X11, not Wayland)
# install stuff, e.g. a desktop file, set icon on Windows # install stuff, e.g. a desktop file, set icon on Windows
install.install() install.install()

View File

@@ -1,9 +1,10 @@
import PyInstaller.__main__ import PyInstaller.__main__
import os import os
import sys
os.system("git -C . describe --match \"*.*.*\" --tags > version.txt") os.system("git -C . describe --match \"*.*.*\" --tags > version.txt")
PyInstaller.__main__.run([ arguments = [
'krowlog.py', 'krowlog.py',
# '--onefile', # '--onefile',
'--noconfirm', '--noconfirm',
@@ -14,10 +15,14 @@ PyInstaller.__main__.run([
'--add-binary', 'icons' + os.pathsep + 'icons', '--add-binary', 'icons' + os.pathsep + 'icons',
'--add-binary', 'locales' + os.pathsep + 'locales', '--add-binary', 'locales' + os.pathsep + 'locales',
'--add-binary', 'LICENSE' + os.pathsep + '.', '--add-binary', 'LICENSE' + os.pathsep + '.',
'--add-binary', 'changelog.txt' + os.pathsep + '.',
'--add-binary', 'version.txt' + os.pathsep + '.', '--add-binary', 'version.txt' + os.pathsep + '.',
'--hidden-import=krowlog', '--hidden-import=krowlog',
'--hidden-import=watchdog', '--hidden-import=__future__',
'--hidden-import=watchdog.observers',
'--hidden-import=watchdog.version',
'--hidden-import=configparser' '--hidden-import=configparser'
]) ]
if sys.platform == 'win32' or sys.platform == 'cygwin':
arguments.append('--version-file=version.py')
PyInstaller.__main__.run(arguments)

View File

@@ -1,5 +1,6 @@
pip==25.0.1 pip==25.0.1
PySide6_Essentials==6.8.2.1 PySide6_Essentials==6.8.2.1
setuptools==77.0.3 setuptools==77.0.3
watchdog==6.0.0
pyinstaller==6.12.0 pyinstaller==6.12.0
isort==6.0.1
black==25.1.0

View File

@@ -0,0 +1,79 @@
import textwrap
import PySide6
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPalette
from PySide6.QtWidgets import *
import constants
import krowlog
from src.ui.icon import Icon
from src.ui.label import Label
from src.ui.vbox import VBox
from src.i18n import _
class AboutQTDialog(QDialog):
"""Dialog for showing info about KrowLog"""
def __init__(self, parent=None):
super(AboutQTDialog, self).__init__(parent)
self.setWindowTitle(_("About QT"))
self.setModal(True)
# self.setMinimumWidth(850)
# self.setFixedHeight(400)
self.layout = QVBoxLayout(self)
text = f"""
<b>About QT</b>
<p>This program uses QT version {PySide6.QtCore.__version__}.</p>
<p>QT is a C++ toolkit for cross-platform application development.</p>
<p>Qt provides single-source portability across all major desktop
operating systems. It is also available for embedded Linux and other
embedded and mobile operating systems.</p>
<p>Qt is available under multiple licensing options designed to accommodate
the needs of our various users.</p>
<p>Qt licensed under our commercial license agreement is appropriate for
development of proprietary/commercial software where you do not want to
share any source code with third parties or otherwise cannot comply with
the terms of GNU (L)GPL.</p>
<p>Qt licensed under GNU (L)GPL is appropriate for the development of Qt
applications provided you can comply with the terms and conditions of the
respective licenses.</p>
Please see <a href="http://qt.io/licensing">qt.io/licensing</a> for an<
overview of Qt licensing.
<p>Copyright (C) 2025 The Qt Company Ltd and other contributors.</p>
<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>
<p>Qt is The Qt Company Ltd product developed as an open source project.
See <a href="http://qt.io">qt.io</a> for more information.</p>
"""
label = Label(text)
label.setWordWrap(True)
app_icon = QLabel()
app_icon.setPixmap(Icon(constants.qt_icon).pixmap(64, 64))
heading = QWidget(self)
hbox = QHBoxLayout(heading)
hbox.addWidget(app_icon)
hbox.addWidget(label)
hbox.addSpacerItem(QSpacerItem(1, 1, hData=QSizePolicy.Policy.Expanding))
heading.layout = hbox
self.layout.addWidget(heading)
buttons = QDialogButtonBox(self)
buttons.setStandardButtons(QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(self.close)
self.layout.addWidget(buttons)
self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)

View File

@@ -1,7 +1,6 @@
import textwrap import textwrap
import PySide6 import PySide6
from watchdog import version as watchdog_version
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPalette from PySide6.QtGui import QFont, QPalette
from PySide6.QtWidgets import * from PySide6.QtWidgets import *
@@ -22,8 +21,8 @@ class AboutDialog(QDialog):
super(AboutDialog, self).__init__(parent) super(AboutDialog, self).__init__(parent)
self.setWindowTitle(_("About KrowLog")) self.setWindowTitle(_("About KrowLog"))
self.setModal(True) self.setModal(True)
self.setMinimumWidth(650) self.setMinimumWidth(850)
self.setFixedHeight(300) self.setFixedHeight(400)
self.layout = QVBoxLayout(self) self.layout = QVBoxLayout(self)
@@ -50,6 +49,7 @@ class AboutDialog(QDialog):
tabs.addTab(self._about(), _("About")) tabs.addTab(self._about(), _("About"))
tabs.addTab(self._libraries(), _("Libraries")) tabs.addTab(self._libraries(), _("Libraries"))
tabs.addTab(self._license(), _("License")) tabs.addTab(self._license(), _("License"))
tabs.addTab(self._changelog(), _("Changelog"))
self.layout.addWidget(tabs) self.layout.addWidget(tabs)
@@ -74,11 +74,9 @@ class AboutDialog(QDialog):
<ul> <ul>
<li>PySide6-Essentials {pyside} (LGPL v3) - <a href="https://doc.qt.io/qtforpython-6/">https://doc.qt.io/qtforpython-6/</a></li> <li>PySide6-Essentials {pyside} (LGPL v3) - <a href="https://doc.qt.io/qtforpython-6/">https://doc.qt.io/qtforpython-6/</a></li>
<li>Qt6 {qt} (LGPL v3) - <a href="https://code.qt.io/cgit/qt/qtbase.git/">https://code.qt.io/cgit/qt/qtbase.git/</a></li> <li>Qt6 {qt} (LGPL v3) - <a href="https://code.qt.io/cgit/qt/qtbase.git/">https://code.qt.io/cgit/qt/qtbase.git/</a></li>
<li>watchdog {watchdog} (Apache 2.0) - <a href="https://github.com/gorakhargosh/watchdog">https://github.com/gorakhargosh/watchdog</a></li>
</ul>""".format( </ul>""".format(
pyside=PySide6.__version__, pyside=PySide6.__version__,
qt=PySide6.QtCore.__version__, qt=PySide6.QtCore.__version__)
watchdog=watchdog_version.VERSION_STRING)
label = textwrap.dedent(dependencies) label = textwrap.dedent(dependencies)
result = QWidget() result = QWidget()
@@ -106,3 +104,25 @@ class AboutDialog(QDialog):
panel.setBackgroundRole(QPalette.ColorRole.Light) panel.setBackgroundRole(QPalette.ColorRole.Light)
result.layout.addWidget(panel) result.layout.addWidget(panel)
return result return result
def _changelog(self) -> QWidget:
with open(constants.changelog_file, 'r') as file:
text = file.read()
result = QWidget()
result.layout = QVBoxLayout(result)
result.layout.setContentsMargins(0, 0, 0, 0)
result.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
label = Label(text)
label.setFont(QFont("Monospace"))
label.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
panel = QScrollArea(result)
panel.setContentsMargins(0, 0, 0, 0)
panel.setViewportMargins(0, 0, 0, 0)
panel.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
panel.setWidget(label)
panel.setBackgroundRole(QPalette.ColorRole.Light)
result.layout.addWidget(panel)
return result

View File

@@ -5,6 +5,7 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QDockWidget, QMessageBox from PySide6.QtWidgets import QDockWidget, QMessageBox
import constants import constants
from src.plugins.krowlog.about_qt_dialog import AboutQTDialog
from src.plugins.krowlog.aboutdialog import AboutDialog from src.plugins.krowlog.aboutdialog import AboutDialog
from src.mainwindow import MainWindow from src.mainwindow import MainWindow
from src.pluginbase import PluginBase from src.pluginbase import PluginBase
@@ -36,6 +37,7 @@ class KrowLogPlugin(PluginBase):
return [ return [
MenuContribution("file", action=self._action_close(), action_id="close application", after="<last>"), MenuContribution("file", action=self._action_close(), action_id="close application", after="<last>"),
MenuContribution("help", action=self._action_about(), action_id="open about dialog", after="<last>"), MenuContribution("help", action=self._action_about(), action_id="open about dialog", after="<last>"),
MenuContribution("help", action=self._action_about_qt(), action_id="open about QT dialog", after="<last>"),
MenuContribution("settings", menu=self._sub_menu_languages(), action_id="recent files menu"), MenuContribution("settings", menu=self._sub_menu_languages(), action_id="recent files menu"),
] ]
@@ -108,6 +110,14 @@ class KrowLogPlugin(PluginBase):
) )
return about_action return about_action
def _action_about_qt(self) -> RAction:
action = RAction(
_("&About QT"),
action=lambda: AboutQTDialog().exec(),
icon_file=constants.qt_icon
)
return action
def _action_close(self) -> RAction: def _action_close(self) -> RAction:
icon = "close" if sys.platform == 'win32' or sys.platform == 'cygwin' else "exit" icon = "close" if sys.platform == 'win32' or sys.platform == 'cygwin' else "exit"
close_action = RAction(_("E&xit"), action=lambda: self.main_window.destruct(), shortcut='Ctrl+X', close_action = RAction(_("E&xit"), action=lambda: self.main_window.destruct(), shortcut='Ctrl+X',

View File

@@ -279,6 +279,7 @@ class FilterWidget(QWidget):
def destruct(self): def destruct(self):
self._cancel_search() self._cancel_search()
self.hits_view.destruct()
os.remove(self.tmp_filename) os.remove(self.tmp_filename)
def _cancel_search(self): def _cancel_search(self):

View File

@@ -23,55 +23,31 @@ from src.ui.icon import Icon
from src.ui.rangeslider import RangeSlider from src.ui.rangeslider import RangeSlider
from src.util.conversion import humanbytes from src.util.conversion import humanbytes
from src.pluginregistry import PluginRegistry from src.pluginregistry import PluginRegistry
from threading import Event
from src.settings.settings import Settings
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from src.i18n import _ from src.i18n import _
log = logging.getLogger("bigtext") log = logging.getLogger("bigtext")
class FileObserver(FileSystemEventHandler):
def __init__(self, big_text):
super(FileObserver, self).__init__()
self.big_text = big_text
self._last_mtime = -1
def on_modified(self, event):
# slow down the updates. This is needed, because the file is modified
# constantly, which would lead to constant re-rendering, which would
# block the UI thread and make the UI unresponsive.
# Note: we don't miss events, because they are queued and de-duplicated
if not event.is_directory:
try:
mtime = os.stat(event.src_path).st_mtime
if mtime != self._last_mtime:
self._last_mtime = mtime
time.sleep(0.5)
self.big_text.trigger_update.emit()
except FileNotFoundError:
# ignore: happens when closing the application, because tmp files are deleted,
# which triggers a modification event
pass
class FileWatchdogThread(QRunnable): class FileWatchdogThread(QRunnable):
def __init__(self, big_text, file: str): def __init__(self, big_text, file: str):
super(FileWatchdogThread, self).__init__() super(FileWatchdogThread, self).__init__()
self.file = file self.file = file
self.big_text = big_text self.big_text = big_text
self.observer = Observer() self.stop = Event()
def run(self) -> None: def run(self) -> None:
self.observer.schedule(FileObserver(self.big_text), self.file) _last_mtime = None
self.observer.start() while not self.stop.is_set():
mtime = os.stat(self.file).st_mtime
if mtime != _last_mtime:
_last_mtime = mtime
self.big_text.trigger_update.emit()
self.stop.wait(0.5)
def destruct(self): def destruct(self):
self.observer.stop() self.stop.set()
# self.observer.join(1)
class BigText(QWidget): class BigText(QWidget):
@@ -503,7 +479,7 @@ class InnerBigText(QWidget):
# # print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte)) # # print("%s + %s = %s" % (line.byte_offset(), char_in_line, current_byte))
else: else:
current_byte = self.model.byte_count() current_byte = self.model.byte_count()
return current_byte return SelectionPos(current_byte, True, 1)
def elided_text(self, text: str, width: int): def elided_text(self, text: str, width: int):
w = width + self.font_metric.horizontalAdvance("") w = width + self.font_metric.horizontalAdvance("")
@@ -581,7 +557,6 @@ class InnerBigText(QWidget):
def _toggle_follow(self): def _toggle_follow(self):
self._follow = not self._follow self._follow = not self._follow
print(f"follow={self._follow}")
self.update() self.update()
def _update_highlight_selected_text(self): def _update_highlight_selected_text(self):

View File

@@ -15,7 +15,7 @@ class Line:
self._cache_char_to_column() self._cache_char_to_column()
def get_width_in_px(self, font_metric: QFontMetrics): def get_width_in_px(self, font_metric: QFontMetrics):
return font_metric.horizontalAdvance(self._line) return font_metric.horizontalAdvance(self.line_prepared_for_display())
def byte_offset(self) -> int: def byte_offset(self) -> int:
return self._byte_offset return self._byte_offset
@@ -43,7 +43,8 @@ class Line:
return len(prefix_chars) return len(prefix_chars)
def line_prepared_for_display(self) -> str: def line_prepared_for_display(self) -> str:
line = self._line_tabs_replaced() # line = self._line_tabs_replaced()
line = self._line
line = self._replace_control_chars_with_pictures(line) line = self._replace_control_chars_with_pictures(line)
return line return line
@@ -108,7 +109,7 @@ class Line:
if not result in self._column_to_char_cache: if not result in self._column_to_char_cache:
self._column_to_char_cache[result] = i self._column_to_char_cache[result] = i
current_char = self._line[i] current_char = self._line[i]
if current_char == "\t": if False and current_char == "\t":
result = result + constants.tab_width - result % constants.tab_width result = result + constants.tab_width - result % constants.tab_width
else: else:
result = result + 1 result = result + 1

View File

@@ -0,0 +1,11 @@
class SelectionPos:
def __init__(self, index: int, is_in_left_half: bool, num_bytes_of_char: int):
self.index = index
self.is_in_left_half = is_in_left_half
self.num_bytes_of_char = num_bytes_of_char
def __repr__(self):
return f"{self.index}{'🞀' if self.is_in_left_half else '🞂'}({self.num_bytes_of_char})"
def pos(self):
return self.index + (0 if self.is_in_left_half else self.num_bytes_of_char)

View File

@@ -78,20 +78,6 @@ class MyTestCase(unittest.TestCase):
self.assertEqual(2, line.char_to_column(4)) # z̈ self.assertEqual(2, line.char_to_column(4)) # z̈
self.assertEqual(2, line.char_to_column(5)) # z̈ self.assertEqual(2, line.char_to_column(5)) # z̈
def test_line_tabs_replaced(self):
byte_offset = 123
text = "\ta\tb" # will be rendered as: ....abc where . represents a whitespace column
expected = " a b"
line = Line(byte_offset=byte_offset, byte_end=byte_offset + len(text.encode("utf8")), line=text)
self.assertEqual(expected, line.line_prepared_for_display())
def test_line_tabs_replaced_performance(self):
byte_offset = 123
text = "a\t" * 10000
expected = "a " * 10000
line = Line(byte_offset=byte_offset, byte_end=byte_offset + len(text.encode("utf8")), line=text)
self.assertEqual(expected, line.line_prepared_for_display())
def test_byte_index_to_char_index(self): def test_byte_index_to_char_index(self):
byte_offset = 123 byte_offset = 123
text = "x\u0308y\u0308z\u0308\t\u0308a" text = "x\u0308y\u0308z\u0308\t\u0308a"

View File

@@ -177,3 +177,7 @@ ä---------ä----------ä---------ä----------ä---------ä----------ä--
17 17
18 18
19 19
アンドレアス
アンドレアス

28
version.py Normal file
View File

@@ -0,0 +1,28 @@
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(0, 2, 1, 0),
prodvers=(0, 2, 1, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u''),
StringStruct(u'FileDescription', u'KrowLog is a viewer for log files of arbitrary size.'),
StringStruct(u'FileVersion', u'0.2.1' ),
StringStruct(u'InternalName', u'krowlog'),
StringStruct(u'LegalCopyright', u'\xa9 Andreas Huber'),
StringStruct(u'OriginalFilename', u'krowlog.exe'),
StringStruct(u'ProductName', u'KrowLog'),
StringStruct(u'ProductVersion', u'0.2.1-dev')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)

View File

@@ -1 +0,0 @@
0.2.1-36-g9902be0