HC Text Editor
This is a Python script for editing text files that uses Qt for the graphical user interface. It provides syntax highlighting for PageStream text codes.
Requirements
- Python 2.6 or newer
- PySide 1.0.4
(or newer at your own risk)
Known Issues
I really hate to put something like this up front, but PySide versions after 1.0.4 introduced various bugs. Fun things like when you save a file it actually makes it empty. This text is being typed on Linux using PySide 1.1.0 and it is actually saving the file, but typing is slow. Due to time constraints the performance issues have not been pursued.
Installation
In addition to the script files included in the archive both Python and PyQt are required.
Linux
Use your distributions package manager to install Python and PySide. Python is probably already installed, PySide most likely will not be. For Ubuntu (or any debian derived distro) you can use sudo add-apt-repository ppa:pyside, sudo apt-get update and then sudo apt-get install python-pyside. In general, go to www.pyside.org and click the download link.
Windows
To get python try www.python.org and follow the download link in the upper left. You will need a 2.x version of Python (3.x is not backwards compatible). PySide can be obtained from developer.qt.nokia.com—all that is needed is a binary version to match the installed version of Python. If you have installed Python 2.7 then you will need a version that ends with Py2.7.
OS X
Python is already installed, but PySide must be downloaded and installed. It can be obtained from developer.qt.nokia.com—all that is needed is a binary version to match the installed version of Python. For most people this will be a version ending in apple.pkg indicating it is for use with the Apple provided Python. PySide also provides a version for the official version of Python.
Using
So its a text editor, what documentation do you need to use it? Well, specifically, this is an implementation of the Qt TextEdit widget so all of its capabilities are present. This gives a solid, though basic, text editor. In addition, some basic syntax highlighting has been implemented with a focus on PageStream file formats and a few niceties for programmers. Note that the menu structure may vary by platform—for example, on OS X the Preference menu item is moved from Tools to the Apple menu.
Profiles
HCTextEditor uses the concept of profiles for determining settings for displaying and editing a document. Profiles are included for PageStream text codes or resource files, python scripts, HTML, rich text, and plain text. Profile settings are loaded when the application is run and any changes made via the Preferences dialog are saved when the application is closed. A profile can be restored to the built in default settings using the Restore Defaults button in the Preferences dialog.
Tabstop
There are two settings for tabs: the number of spaces represented by a tab character and whether or not to convert tabs to spaces. For normal text editing it is desirable (and often necessary) to leave tabs alone. However, when maintaining a proper indentation scheme tabs complicate matters because different editors will display the tabs differently. When tabs are converted to spaces the editor will still delete them as if they were tabs, minimizing the number of key strokes required when manually adjusting the indentation of a line.
Maintain Indent
Some file formats are more readable if the contents follow a consistent indentation. This is true of program source files in general and Python in particular requires it. PgSTextEditor provides two methods for automatically indenting lines: "normal" and "smart".
Normal indentation simply keeps the current level. If the line is indented four spaces, when return is hit the new linen will automatically be indented four spaces.
Smart indentation increases and decreases the level of indent based on the profile. For example, in the Python profile if a line ends with a colon the next line has the level of indent increased by one tabset. This is a convenience to minimize the amount of manual tabbing (and tab deletion) that a programmer needs to do.
Macros
A macro is a sequence of key strokes that can be played back at arbitrary times in the future. Macros are recorded literally—they are subject to interpretation for the current rules of the profile. For example, recording a macro with tabs will result in tab characters if the profile does not expand tabs, but in spaces if the profile expands the tab.
A recorded macro is stashed by the document window that it was recorded in. If multiple document windows are open the macro will not be available in other windows. A stashed macro is lost when the application is closed. However, a named macro can be used in any document window and will be available the next time the application is started.
To name a stashed macro use the Manage dialog from the Macro menu. The first entry will be the stashed macro, if any, and will be named "current macro". Select that entry and change the name in the edit line field. A new entry will be immediately added with that name. To delete a macro select it and click the delete button. To undo any changes made in the dialog click the "Reset" button.
Character Encoding
First and foremost this text editor is conceived as being used for UTF-8 encoded text. That is the default codec used when creating a new document or opening an existing document. However, it is possible to select a different codec from the Character Encoding Tool menu. As a codec is only applied when loading or saving a file the file is automatically reloaded whenever the codec is changed. To avoid losing changes the menu is ghosted if the file is modified.
Insert Date
The current date/time can be inserted using the edit menu item of that name. Currently there is no keyboard shortcut for this action. The format to use can be set with the Set Date Format... menu action and is saved along with preferences when the document is closed. The format specifiers are indicated in a combobox which can be used to insert them. Fuller explanations are available as tooltips, but regrettfully only after they have been inserted. The table is replicated here.
| %a: wkdy | Locale's abbreviated weekday name |
| %A: Weekday | Locale's full weekday name |
| %b: mon | Locale's abbreviated month name |
| %B: Month | Locale's full month name |
| %c: localized date/time | Locale's appropriate date and time representation |
| %d: day | Day of the month as a decimal number |
| %f: μ seconds | Microsecond as a decimal number, zero-padded on the left |
| %H: 24H | Hour (24-hour clock) as a decimal number |
| %I: 12H | Hour (12-hour clock) as a decimal number |
| %j: day of year | Day of the year as a decimal number |
| %m: mo | Month as a decimal number |
| %M: mi | Minute as a decimal number |
| %p: pm | Locale's equivalent of either AM or PM |
| %S: seconds | Second as a decimal number |
| %U: week number | Week number of the year as a decimal number, Sunday as first day of week |
| %w: weekday number | Weekday as a decimal number |
| %W: week number | Week number of the year as a decimal number, Monday as first day of week |
| %x: localized date | Locale's appropriate date representation |
| %X: localized time | Locale's appropriate time representation |
| %y: yr | Last two digits of year |
| %Y: Year | Four digit year |
| %z: UTC offset | UTC offset |
| %Z: tz | Time zone name |
| %%: % | Literal '%' character |
Menus
- File
- New Document
- Create a new document tab in the current window
- New Window
- Create a new document in a new window
- Open
- Open a new file
- Save
- Save the current file—if it has not been previously saved a file dialog will be presented
- Save As
- Save the current file—always opens a file dialog
- Reload
- Reloads the current file from disk
- Print
- Prints the current file—a print dialog is always opened
- Quit
- Closes the application—if there are unsaved changes a dialog will be opened
- Edit
- Undo
- Undoes the last action
- Redo
- Redo the last undone action
- Cut
- Cuts the currently selected text from the document and puts it on the pasteboard
- Copy
- Copies the currently selected text to the pasteboard
- Paste
- Replaces current selection with contents of the clipboard
- Select All
- Selects the entire document
- lowercase
- Converts selected text to all lower case letters
- UPPERCASE
- Converts selected text to all upper case letters
- Capitalize
- Lower cases all selected text, except the first letter of each word which is capitalized
- Increase Indent
- Increases the indentation of the currently selected lines by one tabstop
- Decrease Indent
- Decreases the indentation of the currently selected lines by one tabstop for lines starting with at least one tabstop
- Insert Date
- Inserts current date and time with the current format specification
- Set Date Format...
- Opens a dialog that allows setting the format to use when inserting the date
- Search
- Find...
- Opens the find requester
- Find Selection
- Finds next occurance of currently selected text
- Find Prev Selection
- Finds previous occurance of currently selected text
- Find Next
- Finds next occurance of text to find
- Find Previous
- Finds previous occurance of text to find
- Replace
- Opens the replace requester
- Replace Next
- Replaces the next occurance of text to replace
- Replace All
- Replaces all occurances of the text to replace
- Go to line
- Opens dialog allowing specifying the line to go to by line number
- Macro
- Record
- Starts recording key presses
- Finish
- Stops recording key presses, stashes any that were recorded
- Cancel
- Stops recording key presses, discards any that were recorded
- Replay
- Plays back stashed key presses
- Repeat
- Opens dialog allowing selection of a macro to play and the number of times to play it
- Manage
- Allows naming a stashed macro (only named macros are retained between runs of the application), renaming and deleting macros
- Tools
- Sort
- Sorts currently selected text on a per-line basis
- Preferences...
- Opens dialog for configuring profiles
- Select Font...
- Opens font selection dialog—this only affects the current session
- Reset Font
- Clears the font selection for the current session, returning the font to that set for the style
- Set Tabstop...
- Opens a dialog for setting the tab stop—this only affects the current session
- Show Line Numbers
- Toggles the display of line numbers—this only affects the current session
- Highlight Current Line
- Toggles the backlighting of the current line with the current highlight color
- Highlight Color...
- Opens a dialog for setting the highlight color—this only affects the current session
- Maintain Indent
- Select the method for maintaining indentation—this only affects the current session
- Word Wrap
- Select the method for wrapping words—this only affects the current session
- Select Profile
- Override the automatically selected profile—this only affects the current document
- Select Codec
- Set the code used to interpret the text—this only affects the current document (the default codec is UTF-8)
- Help
- About
- Opens a dialog giving basic information about the application
- Contents
- Displays this file
History
- 1.2.6
- Updated text
- Added PySide caveat
- 1.2.5
- Revised makeBool() to work on OS X which preserves bool
- Consolidated calls to platform.system() into a single global variable to avoid interrupt problems
- Forced Find Next to Ctrl+G due to KDE trying to follow Windows
- 1.2.4
- Added configuration of line number font to preferences
- Added ability to configure default character encoding
- Sort (w/o selection) modifies the document does not create an undo step: rewrote sort function to retain cursor information and ensure undo block creation
- Fixed issue with profile selection
- Corrected issue with font definition being none in a loaded profile
- Corrected issue with loading boolean setting
- 1.2.3
- Switched from PyQt to PySide. Although this is a major change that likely has introduced bugs it simplifies the code significantly (introduced bugs are from having to pythonize the code instead of going through PyQt's contortions) and is expected to provide better crossplatform support, particularly on OS X.
- Regression: macro load and store
- Newer python/qt environments (?) broke profile selection (fixed)
- Segfault when closing with line numbers visible (fixed)
- Regression: fileSave for a new text file was broken (fixed)
- Regression: loading of profile formats from prefs (fixed)
- Regression: checkboxes ceased working due to PySide support of tristate (fixed)
- Regression: recent files would not update (fixed)
- adding new empty document tab causes loss of tab bar (does not happen when opening a document) -- can no longer reproduce, possibly due to PySide?
- Regression: tabset preference not loaded from profile (fixed)
- tab bar sometimes does not refresh, particularly when starting with a list of files (possibly fixed) -- can no longer reproduce, possibly due to PySide?
- find selected does not add to the find lists and reverse direction based on that flag in the search dialog (fixed adding to find history -- undesirable to alter flags/options in find dialog, won't do)
- Regression: fixed another bug (crash loading single file from CurrentFiles setting) introduced by PySide's failure to properly restore single entry lists
- Implemented expanded prefs dialog
- Regression: date format was not being set (fixed)
- Regression: sort no longer works (fixed)
- Regression: Recent files are saved in config and the menu is created as expected, but attempting to open a file from there results in a new document tab instead—this was bug was caused by the official guide on converting from PyQt to PySide (ignore what they say about sender(), it still works)
- Implemented 'smart text' profile (mostly) following traditional email conventions (as overlapping formats are not allowed by the syntax highlighter there are some limitations)
- Files opened after the first file were loaded twice (fixed)
- changed file detection runs away in some cases (which? has not happened yet with an html file, so far always happens with a python file)—this hasn't happened in a while, the only observed issue is a notice that the file changed do you want to reload immediately following multiple saves in rapid succession. This last is most likely a race condition with dependencies on the underlying file system.
- Moved "Set Date Format" functionality to general preferences tab
- 1.2.2
- Unable to open documentation/help file from menu (fixed)
- Corrected handling of specifying non-existent file on the commandline
- Corrected handling of tabs with unsaved changes when closing application (would save without warning)
- Implemented ReadOnly toggle, option is ghosted and document set to not be writeable if the file cannot be written
- File changed dialog now warns user if the current file has unsaved changes when prompting to reload
- Attempting to save a file that has been changed externally will produce a warning dialog about overwriting external changes
- Fixed Reload File function and it now remembers the text cursor position
- Allow specifying non-UTF8 codecs (codec can only be changed if there are no unsaved changes)
- Corrected issue with saving new documents
- Implemented Increase/Decrease indent
- 1.2.1
- Initial tab did not have signals connected for undo, redo and modification changed (fixed)
- Tab and window titling does not match .isModified() status (fixed: probably in previous version)
- Fixed macro repeat dialog
- Added stub for PHP profile
- Blocked closing of empty tab as when first started
- 1.2
- Implemented tabbed documents
- Fixed numerous regression bugs introduced by tabbed documents
- Implemented file change detection
- Separated "text" profile from "default" profile
- Fixed implementation of making a stored macro the current editor macro
- Fixed sorting (only sorted on selection, introduced line split at beginning of selection and merged last line of selection with following line)
- Improved and updated python syntax highlighting
- Find/Replace dialogs always open with focus on the find field
- New tab has focus
- 1.1.3
- Fixed saving of unnamed files (would not do save as, instead saved to current dir without prompting)
- Limited number of files that can be opened on the command line to avoid accidental overload
- Find text in Replace dialog updates Find dialog
- Implemented regular expression support for replace function; supported are back references
- Fixed file filters in open file dialog (requires restoring each filter to default)
- 1.1.2
- Revised syntax highlighter so it can work without look behind assertions
- Corrected profile becoming lost problem when creating new windows
- Changed profile handling so all profiles are stored in highlighter in order to simplify code and lower overhead when switching profiles
- Revised some default formatting for Python profile (must restore to defaults to see)
- Added explicit text profile
- Added preliminary RTF profile (based on 1.5 and not rigorously tested)
- Proper implementation of multiple open windows (Single Document Interface)
- 1.1.1
- Moved editor code back into HCTextEditor.pyw (and so the wheel keeps on turning)
- 1.1
- Fixed a few minor bugs
- Moved editor code into a module to facilitate the PgS Text Editor project
- 1.0.13
- Implemented insert date function per request
- Renamed project to "HC Text Editor"
- 1.0.12
- Implemented repeat and manage macro dialogs, named macros are now saved and loaded in the global preferences
- Fixed long standing bug relating to profiles
- Corrected save designation for the settings
- 1.0.11
- Made platform-dependent GUI tweak work by detecting platform rather than being hard coded
- New File menu option to open a new document window, thus allowing multiple documents to be open at one time
- Implemented "Close" in file menu, same functionality as Quit—eventually Quit will close all open windows
- fixed bug that caused top line number to sometimes be doubled with line number for previous (non-visible) line
- Implemented macro recording and playback. Repeat and Manage are still not done.
- 1.0.10
- style names with a space in them were not recognized (fixed)
- setting font for current session didn't work (fixed)
- added menu item to reset font to the style
- replace menu function broken (fixed)
- replace all button added to replace dialog
- 1.0.9
- fixed some bugs with find/replace text history
- Implemented regular expressions for find search terms (though not replace)
- fixed bugs in looping find past beginning or end of document
- 1.0.8
- Implemented setting profile font
- Implemented setting syntax highlighting color and emphasis
- Implemented highlighting of current line (disabled due to issues with the undo/redo stack)
- Implemented find/replace text history
- 1.0.7
- Fixed problem using backspace on an empty line
- Initial implementation of profile preferences dialog
- User selection of word wrap
- Re-implemented line numbering from scratch due to performance issues
- Undo/Redo menu items are enabled/disabled depending on availability of such actions
- Implemented cursor position information in status bar
- Implemented printing
- Implemented sort
- 1.0.6
- Fixed case insensitivity in profile regexs
- Kludge to fix some cases where comment syntax highlighting was erroneously applied
- Profile selected for opened file is now indicated in Tools/Profile menu
- Fixed various issues with applying profiles
- No longer retains formatting of pasted text
- Line numbering implemented
- Kerning is disabled if fixed pitch is enabled
- Smart and normal methods for maintaining indent implemented along with menu item for selection
- 1.0.5
- Implemented find selection backwards
- Implemented reload file
- Added full file path for recent files to status bar
- Improved handling of recent files
- Tweaked python syntax highlighter
- Can now match multiple extensions for a profile (e.g., both .html and .htm)
- 1.0.4
- Code cleanup
- restored separate setFont() function
- Initial support for converting tabs to spaces
- Saving and restoring the set font between sessions
- implemented Find/Find Next/Find Selection
- implemented Replace/Replace Next/Replace Prev/Replace All
- implemented tabstop dialog and setting storage
- implemented smart tab deletion when deleting tabs converted to spaces
- improved syntax highlighting
- added python, html and PgS resource file extension support to openfile
- implemented profiles: default, PgS text codes, python and html (support for PgS resource files)
- added profile selection to menu, current profile is checked
- 1.0.3
- fixed bug causing hang on OS X
- updated documentation
- implemented help menu
- add stubs for search, macro and tools menus
- moved setfont to tools menu
- implemented recent files list
- settings saved between invocations (window size and placement, recent files)
- groundwork for preferences to customize color for highlighting
- added undo/redo and select all to Edit menu
- added lower(), upper() and capitalize() to Edit menu
- 1.0.2
To Do
Outstanding features to implement in no particular order:
- allow for "smart" beginning of line (e.g., start of line goes before first non-whitespace, then start of line)
- implement substition for \r, \n, \t, etc. in regular expression replacements
- Add optional visual guide (vertical rule) to indicate desired maximum column position
- add more actions to toolbar
- move recent files from bottom of file menu to a submenu
- refactor search functions
- implement short cuts for named macros
- shortcut for increase/decrease indent
- Consider attempting to merge documents when both have changed
- Improve cases where file change is detected
- Improve update of tabWidget
- Context menu for tab bar to close/detach/move to new window
- Resolve default profile vs direct tools menu access to settings
- Create icons for the different file types and display them in the tabs (also create prefs item to control display)
- Add more profiles (e.g., perl, css, bash)
- Minimize impact of updateUi() calls
- Implement shortcut for Macro Repeat... dialog
- Extend macros so that they can refer to other macros with an iteration count (e.g., macro1 plays macro2 four times, then macro3)
- Implement collapsing of code blocks via QTextBlock.setVisible()
- If the current line highlighting is enabled then the highlighting being activated counts as an action to be undone, use QTextCursor.joinPreviousEditBlock() and endEditBlock() to bracket the highlight action and join with last operation?
- Add line number bar font selection to general prefs
- Convert old-style signal connect() statements to new-style (warning: major regressions ahead—thoroughly test every altered connect)
- Consider detecting binary files on open (how?) and setting them to read only by default as while there is value to opening a binary file for viewing there generally isn't a desire to edit them—on the other hand preventing editing would not seem to serve a useful purpose either.
- Implement handling of various line endings: "save as Windows|DOS|Unix" or "convert to Windows|DOS|Unix"
Wish List
These are desired, but require an inordinate amount of time to implement.
- implement status bar text for undo/redo actions
Bugs
- trailing spaces causes incorrect smart "tab" deletion
- trailing spaces causes smart indentation to not work
- Tools menu settings that override a profile are lost by switching between tabs or just clicking in the window (presumably any updateUi() call)
- Left Gap (number bar prefs) has no effect
- Decrease indent will not remove the last tab stop (OSX 1.2.4/1.0.4) [appears to be issue with corrupted preferences thanks to QSettings() that removes tabstop settings—restoring profile to defaults resolved issue]
- Sort doesn't seem to work. -- Theo (Windows 1.2.3/1.0.?)
Limitations
- PyQt version no longer works on OS X. So far the underlying cause is not known, but is likely related to Python and PyQt and underscores the necessity of coming up with a suitable way to package the application. Use the PySide version instead which is actually updated for OS X.
- Doing decent html syntax highlighting would require a much more complicated scheme than is currently implemented. There are no plans to improve on the current implementation.
- Although most settings are saved, the profile overrides them and is automatically set whenever a file is loaded. Consequently there isn't much point to saving the non-profile settings.
- As of Qt4.6 there is no way to get the undo/redo text out of the undo/redo command stack—this is hidden inside the QTextEdit implementation. The logical place for it (other than simply exposing the stack) would be to add a parameter to QTextDocument::undoCommandAdded() signal that contained the undo command added to the stack, or at least the hint text for it.