This document defines the coding conventions for DarkDraw, following VisiData patterns and Python best practices.
Always order imports in this sequence, with groups separated by a single blank line:
- Standard library imports
- VisiData imports
- Third-party imports (if any)
- Internal darkdraw imports
Example:
import itertools
from collections import defaultdict
from copy import copy
from visidata import vd, CharBox, dispwidth
from visidata.bezier import bezier
from darkdraw import Drawing- Module-level constants and configuration first (vd.option, globals)
- Helper functions and utilities
- Class definitions
- Command/menu registrations at end
- Functions/variables:
snake_case - Classes:
CamelCase - Private/internal: Leading underscore
_private_var
Use the @ClassName.api decorator to add methods to VisiData or DarkDraw classes in other modules:
@Drawing.api
def my_method(sheet, arg):
# method implementation- Use
@ClassName.propertyfor computed values - Use
@functools.cached_propertyfor expensive computations - Use
@drawcache_propertyto cache properties that don't change within one draw cycle
Register commands with addCommand():
Sheet.addCommand('gCtrl+S', 'save-selected', 'save_selected()', 'save selected rows')Format: addCommand(keystrokes, longname, execstr, helpstr)
Prefer prettykeys format like gCtrl+S to g^S.
Use vd.addMenuItems() with multi-line string format:
vd.addMenuItems('''
DarkDraw > Action > Subaction > context > command-name
''')
When several actions are variants of the same thing, the core concept should be a capitalized word on its own, while subactions or context should be lowercase.
The Action/Subaction and context should form a logical cascade of words or concepts, from more general to more specific.vd.status('message')- informational messagesvd.warning('message')- warningsvd.fail('message')- usage errors (raise exception, don't need return)vd.error('message')- internal errors (raise exception, don't need return)vd.exceptionCaught(e)- show exception traceback within anexcept:block
Always add undo for state changes:
vd.addUndo(setattr, obj, 'attr', old_value)
vd.addUndo(list.remove, mylist, item)vd.addUndo(func, *args, *kwargs) will call func(*args, *kwargs) to undo the action.
Where reasonable, call addUndo before making the change that will be undone.
- Indentation: 4 spaces (no tabs)
- Line Length: Aim for <100 characters (no hard limit, prefer readability)
- Comments: use
#(space after hash) for inline comments - Reference issues with
#123format as needed - Keep comments concise and meaningful
- Prefer f-strings for new code:
f"value: {x}"
AttrDict is used for more convenient dict key access:
row = AttrDict(x=0, y=0, text='', color='', tags=[], group='')
if row.x == 0:
...- Not currently used extensively
- Optional for new code
- Focus on clarity over type completeness
- type imports not preferred; use type strings when necessary
- Use for complex functions and classes
- Inline rowdef documentation:
rowtype='elements' # rowdef: { .x, .y, .text } - Keep docstrings brief and practical
- Explain why, not what
- Use for non-obvious logic
- Avoid over-commenting obvious code
- Avoid over-engineering
- Don't add features not explicitly needed
- Keep functions focused and single-purpose
- Use
Progress()wrapper on iterators in loops within@asyncthreadfunctions (or called by asyncthread functions)
- Mark sheets as modified:
self.setModified() - Prefer immutable operations where practical