visidata 3.1.1__py3-none-any.whl → 3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. visidata/__init__.py +2 -2
  2. visidata/_input.py +106 -58
  3. visidata/_open.py +10 -7
  4. visidata/_types.py +2 -2
  5. visidata/aggregators.py +125 -16
  6. visidata/apps/vdsql/_ibis.py +8 -13
  7. visidata/basesheet.py +4 -3
  8. visidata/canvas.py +11 -7
  9. visidata/clipboard.py +11 -2
  10. visidata/cliptext.py +68 -23
  11. visidata/cmdlog.py +5 -1
  12. visidata/column.py +48 -33
  13. visidata/ddwplay.py +2 -2
  14. visidata/deprecated.py +96 -63
  15. visidata/errors.py +41 -5
  16. visidata/{features → experimental}/helloworld.py +1 -1
  17. visidata/experimental/liveupdate.py +1 -1
  18. visidata/expr.py +1 -0
  19. visidata/extensible.py +4 -0
  20. visidata/features/cmdpalette.py +64 -25
  21. visidata/features/describe.py +2 -2
  22. visidata/features/expand_cols.py +7 -5
  23. visidata/features/freeze.py +14 -2
  24. visidata/features/go_col.py +3 -3
  25. visidata/features/graph_zoom_y.py +47 -0
  26. visidata/features/incr.py +7 -3
  27. visidata/features/join.py +23 -12
  28. visidata/features/layout.py +8 -4
  29. visidata/features/melt.py +1 -0
  30. visidata/features/rank.py +103 -0
  31. visidata/features/reload_every.py +11 -8
  32. visidata/features/sysedit.py +14 -4
  33. visidata/features/transpose.py +1 -0
  34. visidata/features/window.py +12 -0
  35. visidata/form.py +10 -9
  36. visidata/freqtbl.py +47 -3
  37. visidata/fuzzymatch.py +11 -7
  38. visidata/graph.py +5 -3
  39. visidata/guides/AggregatorsSheet.md +84 -0
  40. visidata/guides/CommandsSheet.md +1 -0
  41. visidata/guides/MacrosSheet.md +1 -1
  42. visidata/guides/RankGuide.md +51 -0
  43. visidata/guides/TypesSheet.md +1 -1
  44. visidata/guides/WindowFunctionGuide.md +49 -0
  45. visidata/help.py +23 -6
  46. visidata/indexsheet.py +1 -1
  47. visidata/loaders/_pandas.py +3 -1
  48. visidata/loaders/archive.py +33 -6
  49. visidata/loaders/csv.py +12 -1
  50. visidata/loaders/eml.py +2 -0
  51. visidata/loaders/f5log.py +2 -2
  52. visidata/loaders/fec.py +6 -9
  53. visidata/loaders/fixed_width.py +2 -0
  54. visidata/loaders/hdf5.py +34 -10
  55. visidata/loaders/npy.py +54 -23
  56. visidata/loaders/orgmode.py +3 -2
  57. visidata/loaders/pandas_freqtbl.py +4 -0
  58. visidata/loaders/psv.py +13 -0
  59. visidata/loaders/sqlite.py +1 -1
  60. visidata/loaders/vds.py +3 -4
  61. visidata/macros.py +5 -4
  62. visidata/main.py +21 -11
  63. visidata/mainloop.py +8 -5
  64. visidata/man/parse_options.py +3 -2
  65. visidata/man/vd.1 +38 -17
  66. visidata/man/vd.txt +47 -17
  67. visidata/menu.py +10 -10
  68. visidata/metasheets.py +3 -3
  69. visidata/mouse.py +3 -0
  70. visidata/movement.py +6 -3
  71. visidata/pyobj.py +17 -9
  72. visidata/save.py +10 -2
  73. visidata/selection.py +29 -18
  74. visidata/settings.py +9 -5
  75. visidata/sheets.py +124 -48
  76. visidata/shell.py +2 -2
  77. visidata/sidebar.py +11 -8
  78. visidata/sort.py +89 -11
  79. visidata/statusbar.py +10 -9
  80. visidata/tests/test_cliptext.py +164 -0
  81. visidata/tests/test_commands.py +6 -2
  82. visidata/tests/test_menu.py +1 -1
  83. visidata/textsheet.py +34 -8
  84. visidata/themes/ascii8.py +2 -2
  85. visidata/themes/light.py +5 -0
  86. visidata/threads.py +38 -8
  87. visidata/utils.py +15 -1
  88. visidata/vendor/__init__.py +0 -0
  89. {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/vd.1 +38 -17
  90. {visidata-3.1.1.data → visidata-3.3.data}/data/share/man/man1/visidata.1 +38 -17
  91. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/METADATA +62 -15
  92. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/RECORD +98 -92
  93. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/WHEEL +1 -1
  94. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/entry_points.txt +1 -0
  95. visidata-3.1.1.data/scripts/vd +0 -6
  96. {visidata-3.1.1.data → visidata-3.3.data}/data/share/applications/visidata.desktop +0 -0
  97. {visidata-3.1.1.data → visidata-3.3.data}/scripts/vd2to3.vdx +0 -0
  98. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/LICENSE.gpl3 +0 -0
  99. {visidata-3.1.1.dist-info → visidata-3.3.dist-info}/top_level.txt +0 -0
visidata/man/vd.txt CHANGED
@@ -108,6 +108,8 @@ DESCRIPTION
108
108
  gz_ number adjust widths of all visible columns to Ar number
109
109
 
110
110
  - (hyphen) hide current column
111
+ g- (hyphen) hide any column that has multiple rows but only one dis‐
112
+ tinct value
111
113
  z- reduce width of current column by half
112
114
  gv unhide all columns
113
115
 
@@ -152,11 +154,12 @@ DESCRIPTION
152
154
  requires example row)
153
155
  z; expr create new column from bash expr, with $columnNames as
154
156
  variables
155
- * regex/subst add column derived from current column, replacing regex
156
- with subst (may include \1 backrefs)
157
- g* gz* regex/subst
157
+ * search Tab replace
158
+ add column derived from current column, replacing search
159
+ regex with replace (may include \1 backrefs)
160
+ g* gz* search Tab replace
158
161
  modify selected rows in current/all visible column(s),
159
- replacing regex with subst (may include \1 backrefs)
162
+ replacing search with replace (may include \1 backrefs)
160
163
 
161
164
  ( g( expand current/all visible column(s) of lists (e.g. [3])
162
165
  or dicts (e.g. {3}) one level
@@ -191,8 +194,8 @@ DESCRIPTION
191
194
  existing sort criteria
192
195
  g[ g] sort ascending/descending by all key columns; replace
193
196
  any existing sort criteria
194
- z[ z] sort ascending/descending by current column; add to ex‐
195
- isting sort criteria
197
+ z[ z] sort ascending/descending by current column; keep higher
198
+ priority sort criteria
196
199
  gz[ gz] sort ascending/descending by all key columns; add to ex‐
197
200
  isting sort criteria
198
201
  " open duplicate sheet with only selected rows
@@ -202,7 +205,8 @@ DESCRIPTION
202
205
  The rows in these duplicated sheets (except deepcopy) are references to
203
206
  rows on the original source sheets, and so edits to the filtered rows
204
207
  will naturally be reflected in the original rows. Use g' to freeze sheet
205
- contents in a deliberate copy.
208
+ contents in a deliberate copy. z' replace current column with a frozen
209
+ copy, with all cells evaluated
206
210
 
207
211
  Editing Rows and Cells
208
212
  a za append blank row/column; appended columns cannot be
@@ -232,6 +236,7 @@ DESCRIPTION
232
236
  f fill null cells in current column with contents of non-
233
237
  null cells up the current column
234
238
  e text edit contents of current cell
239
+ ^O edit contents of current cell in external EDITOR
235
240
  ge text set contents of current column for selected rows to text
236
241
 
237
242
  Commands While Editing Input
@@ -292,6 +297,7 @@ DESCRIPTION
292
297
  + - increase/decrease zoom level, centered on cursor
293
298
  _ (underbar) zoom to fit full extent
294
299
  z_ (underbar) set aspect ratio
300
+ g_ (underbar) Zoom y-axis to fit all visible data points
295
301
  x xmin xmax set xmin/xmax on graph
296
302
  y ymin ymax set ymin/ymax on graph
297
303
  s t u select/toggle/unselect rows on source sheet con‐
@@ -582,7 +588,8 @@ COMMANDLINE OPTIONS
582
588
  files
583
589
  -N, --nothing=T False disable loading
584
590
  .visidatarc and plugin addons
585
- --visidata-dir=str ~/.visidata/ directory to load and
591
+ --visidata-dir=str /home/anja/.config/visidata
592
+ directory to load and
586
593
  store additional files
587
594
  --debug False exit on error and display
588
595
  stacktrace
@@ -702,7 +709,7 @@ COMMANDLINE OPTIONS
702
709
  DirSheet
703
710
  --dir-hidden False load hidden files on
704
711
  DirSheet
705
- --config=Path /home/saul/.visidatarc
712
+ --config=Path /home/anja/.visidatarc
706
713
  config file to exec in
707
714
  Python
708
715
  --play=str file.vdj to replay
@@ -739,7 +746,7 @@ COMMANDLINE OPTIONS
739
746
  --reddit-client-id=str client_id for reddit api
740
747
  --reddit-client-secret=str client_secret for reddit
741
748
  api
742
- --reddit-user-agent=str 3.1dev user_agent for reddit api
749
+ --reddit-user-agent=str 3.3 user_agent for reddit api
743
750
  --zulip-batch-size=int -100 number of messages to
744
751
  fetch per call (<0 to
745
752
  fetch before anchor)
@@ -799,6 +806,8 @@ COMMANDLINE OPTIONS
799
806
  --grep-base-dir=NoneType None base directory for rela‐
800
807
  tive paths opened with
801
808
  sysopen-row
809
+ --hdf5-matrix-enumerate False enumerate matrix rows and
810
+ columns
802
811
  --html-title=str <h2>{sheet.name}</h2>
803
812
  table header when saving
804
813
  to html
@@ -810,6 +819,8 @@ COMMANDLINE OPTIONS
810
819
  cates for https
811
820
  --npy-allow-pickle False numpy allow unpickling
812
821
  objects (unsafe)
822
+ --npy-matrix-enumerate False enumerate matrix rows and
823
+ columns
813
824
  --pcap-internet=str n (y/s/n) if save_dot in‐
814
825
  cludes all internet hosts
815
826
  separately (y), combined
@@ -851,8 +862,6 @@ COMMANDLINE OPTIONS
851
862
  --describe-aggrs=str mean stdev numeric aggregators to
852
863
  calculate on Describe
853
864
  sheet
854
- --hello-world=str ¡Hola mundo! shown by the hello-world
855
- command
856
865
  --incr-base=float 1.0 start value for column
857
866
  increments
858
867
  --ping-count=int 3 send this many pings to
@@ -897,7 +906,7 @@ COMMANDLINE OPTIONS
897
906
  onto sheet stack
898
907
  disp_menu_input … indicator if input required for
899
908
  command
900
- disp_menu_fmt | VisiData {vd.version} | {vd.hintStatus}
909
+ disp_menu_fmt | VisiData {vd.version} | {vd.motd}
901
910
  right-side menu format string
902
911
  disp_float_fmt {:.02f} default fmtstr to format float
903
912
  values
@@ -984,6 +993,8 @@ COMMANDLINE OPTIONS
984
993
  disp_wrap_placeholder … multiline string to indicate
985
994
  truncation
986
995
  disp_multiline_focus True only multiline cursor row
996
+ color_multiline_bottom color of bottom line of multiline
997
+ rows
987
998
  color_aggregator bold 255 white on 234 black
988
999
  color of aggregator summary on
989
1000
  bottom row
@@ -1033,7 +1044,7 @@ COMMANDLINE OPTIONS
1033
1044
  ⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿
1034
1045
  charset to render 2x4 blocks on
1035
1046
  canvas
1036
- disp_pixel_random False randomly choose attr from set of
1047
+ disp_graph_pixel_random False randomly choose attr from set of
1037
1048
  pixels instead of most common
1038
1049
  disp_zoom_incr 2.0 amount to multiply current zoom‐
1039
1050
  level when zooming
@@ -1050,8 +1061,27 @@ COMMANDLINE OPTIONS
1050
1061
  erence lines on graph
1051
1062
  disp_graph_multiple_reflines_char ▒ char to render multiple parallel
1052
1063
  reflines
1053
- disp_expert 0 max level of options and columns
1054
- to include
1064
+ disp_help_flags cmdpalette guides help hints inputfield inputkeys
1065
+ nometacols sidebar
1066
+ list of helper features to enable
1067
+ (space-separated):
1068
+ - "cmdpalette": exec-longname
1069
+ suggestions
1070
+ - "guides": guides in sidebar
1071
+ - "help": help sidebar col‐
1072
+ lapsed by default
1073
+ - "hints": context-sensitive
1074
+ hints on menu line
1075
+ - "inputfield": context-sen‐
1076
+ sitive help for each input field
1077
+ - "inputkeys": input quick
1078
+ reference in sidebar
1079
+ - "nometacols": hide expert
1080
+ columns on metasheets
1081
+ - "sidebar": context-sensi‐
1082
+ tive sheet help in sidebar
1083
+ - "all": enable all helper
1084
+ features
1055
1085
  color_add_pending green color for rows pending add
1056
1086
  color_change_pending reverse yellow color for cells pending modifica‐
1057
1087
  tion
@@ -1189,4 +1219,4 @@ SUPPORTED SOURCES
1189
1219
  AUTHOR
1190
1220
  VisiData was made by Saul Pwanson <vd@saul.pw>.
1191
1221
 
1192
- Linux/MacOS October 13, 2024 Linux/MacOS
1222
+ Linux/MacOS June 13, 2025 Linux/MacOS
visidata/menu.py CHANGED
@@ -18,14 +18,14 @@ vd.theme_option('disp_menu_boxchars', '││──┌┐└┘├┤', 'box cha
18
18
  vd.theme_option('disp_menu_more', '»', 'command submenu indicator')
19
19
  vd.theme_option('disp_menu_push', '⎘', 'indicator if command pushes sheet onto sheet stack')
20
20
  vd.theme_option('disp_menu_input', '…', 'indicator if input required for command')
21
- vd.option('disp_menu_fmt', '| VisiData {vd.version} | {vd.hintStatus}', 'right-side menu format string')
21
+ vd.option('disp_menu_fmt', '| VisiData {vd.version} | {vd.motd}', 'right-side menu format string')
22
22
 
23
23
  BaseSheet.init('activeMenuItems', list)
24
24
  vd.menuRunning = False
25
25
 
26
26
  @VisiData.property
27
27
  def hintStatus(vd):
28
- if vd.options.disp_expert <= 0:
28
+ if vd.wantsHelp('hints'):
29
29
  if int(time.time()/60) % 2 == 0:
30
30
  return 'Alt+H for help menu'
31
31
  else:
@@ -178,9 +178,9 @@ def drawSubmenu(vd, scr, sheet, y, x, menus, level, disp_menu_boxchars=''):
178
178
 
179
179
  maxbinding = 0
180
180
  if vd.options.disp_menu_keys:
181
- maxbinding = max(len(item.binding or '') for item in menus)+1
181
+ maxbinding = max(dispwidth(item.binding or '') for item in menus)+1
182
182
 
183
- w = max(len(item.title) for item in menus)+maxbinding+2
183
+ w = max(dispwidth(item.title) for item in menus)+maxbinding+2
184
184
 
185
185
  # draw borders before/under submenus
186
186
  if level > 1:
@@ -225,11 +225,11 @@ def drawSubmenu(vd, scr, sheet, y, x, menus, level, disp_menu_boxchars=''):
225
225
  mainbinding = vd.prettykeys(revbinds[0])
226
226
 
227
227
  # actually display the menu item
228
- title += ' '*(w-len(pretitle)-len(item.title)+1) # padding
228
+ title += ' '*(w-dispwidth(pretitle)-dispwidth(item.title)+1) # padding
229
229
 
230
230
  menudraw(scr, y+i, x+1, pretitle+title, attr)
231
231
  if maxbinding and mainbinding:
232
- menudraw(scr, y+i, x+1+w-len(mainbinding), mainbinding, attr.update(colors.keystrokes))
232
+ menudraw(scr, y+i, x+1+w-dispwidth(mainbinding), mainbinding, attr.update(colors.keystrokes))
233
233
  menudraw(scr, y+i, x+2+w, titlenote, attr)
234
234
  menudraw(scr, y+i, x+3+w, ls, colors.color_menu)
235
235
 
@@ -317,7 +317,7 @@ def drawMenu(vd, scr, sheet):
317
317
  BUTTON1_RELEASED=vd.nop,
318
318
  BUTTON2_RELEASED=vd.nop,
319
319
  BUTTON3_RELEASED=vd.nop)
320
- x += len(item.title)+2
320
+ x += dispwidth(item.title)+2
321
321
 
322
322
  rightdisp = sheet.options.disp_menu_fmt.format(sheet=sheet, vd=vd)
323
323
  menudraw(scr, 0, x+4, rightdisp, colors.color_menu)
@@ -355,13 +355,13 @@ def drawMenu(vd, scr, sheet):
355
355
 
356
356
  # cmd.helpstr text
357
357
  for i, line in enumerate(helplines):
358
- menudraw(scr, y+i, helpx, ls+' '+line+' '*(helpw-len(line)-3)+rs, helpattr)
358
+ menudraw(scr, y+i, helpx, ls+' '+line+' '*(helpw-dispwidth(line)-3)+rs, helpattr)
359
359
  y += len(helplines)
360
360
 
361
361
  if sidelines:
362
362
  menudraw(scr, y, helpx, ls+' '*(helpw-2)+rs, helpattr)
363
363
  for i, line in enumerate(sidelines):
364
- menudraw(scr, y+i+1, helpx, ls+' '+line+' '*(helpw-len(line)-6)+rs, helpattr)
364
+ menudraw(scr, y+i+1, helpx, ls+' '+line+' '*(helpw-dispwidth(line)-6)+rs, helpattr)
365
365
  y += len(sidelines)+1
366
366
 
367
367
  menudraw(scr, y, helpx, bl+bs*(helpw-2)+br, helpattr)
@@ -371,7 +371,7 @@ def drawMenu(vd, scr, sheet):
371
371
  menudraw(scr, menuy, helpx+2, rsl, helpattr)
372
372
  ks = vd.prettykeys(mainbinding or '(unbound)')
373
373
  menudraw(scr, menuy, helpx+3, ' '+ks+' ', colors.color_menu_active)
374
- menudraw(scr, menuy, helpx+2+len(ks)+3, lsr, helpattr)
374
+ menudraw(scr, menuy, helpx+2+dispwidth(ks)+3, lsr, helpattr)
375
375
  menudraw(scr, menuy, helpx+19, ' '+cmd.longname+' ', helpattr)
376
376
 
377
377
  vd.onMouse(scr, helpx, menuy, helpw, y-menuy+1,
visidata/metasheets.py CHANGED
@@ -38,8 +38,8 @@ Other commands (not specific to Columns Sheet):
38
38
  'passthrough to the value on the source cursorRow'
39
39
  def calcValue(self, srcCol):
40
40
  return srcCol.getDisplayValue(srcCol.sheet.cursorRow)
41
- def setValue(self, srcCol, val):
42
- srcCol.setValue(srcCol.sheet.cursorRow, val)
41
+ def setValue(self, srcCol, val, setModified=True):
42
+ srcCol.setValue(srcCol.sheet.cursorRow, val, setModified=setModified)
43
43
 
44
44
  columns = [
45
45
  ColumnAttr('sheet', type=str),
@@ -134,7 +134,7 @@ globalCommand('gC', 'columns-all', 'vd.push(vd.allColumnsSheet)', 'open Columns
134
134
  Sheet.addCommand('C', 'columns-sheet', 'vd.push(ColumnsSheet(name+"_columns", source=[sheet]))', 'open Columns Sheet: edit column properties for current sheet')
135
135
 
136
136
  # used ColumnsSheet, affecting the 'row' (source column)
137
- ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'toggle selected source columns as key columns')
137
+ ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'set selected source columns as key columns')
138
138
  ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected source columns as key columns')
139
139
 
140
140
  ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected source columns')
visidata/mouse.py CHANGED
@@ -13,8 +13,11 @@ BaseSheet.init('mouseY', int)
13
13
 
14
14
  @VisiData.after
15
15
  def initCurses(vd):
16
+ if not getattr(curses, 'mousemask', None):
17
+ return
16
18
  curses.MOUSE_ALL = 0xffffffff
17
19
  curses.mousemask(curses.MOUSE_ALL if vd.options.mouse_interval else 0)
20
+ curses.def_prog_mode()
18
21
  curses.mouseinterval(vd.options.mouse_interval)
19
22
  curses.mouseEvents = {}
20
23
 
visidata/movement.py CHANGED
@@ -84,9 +84,12 @@ def moveToNextRow(vs, func, reverse=False, msg='no different value up this colum
84
84
  def visibleWidth(self):
85
85
  'Width of column as is displayed in terminal'
86
86
  vcolidx = self.sheet.visibleCols.index(self)
87
- if vcolidx not in self.sheet._visibleColLayout:
88
- self.sheet.calcSingleColLayout(vcolidx)
89
- return self.sheet._visibleColLayout[vcolidx][1]
87
+ if vcolidx in self.sheet._visibleColLayout:
88
+ w = self.sheet._visibleColLayout[vcolidx][1]
89
+ else: #this case should never happen in normal use
90
+ #the width can be inaccurate if the column is not at x=0
91
+ w = self.sheet.calcSingleColLayout(vcolidx)
92
+ return w
90
93
 
91
94
 
92
95
  Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left', replay=False)
visidata/pyobj.py CHANGED
@@ -3,7 +3,7 @@ import inspect
3
3
  import math
4
4
  import numbers
5
5
 
6
- from visidata import vd, asyncthread, ENTER, deduceType
6
+ from visidata import vd, asyncthread, ENTER, deduceType, anytype
7
7
  from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet, setitem
8
8
  import visidata
9
9
 
@@ -46,14 +46,19 @@ def view(vd, obj):
46
46
  vd.run(PyobjSheet(getattr(obj, '__name__', ''), source=obj))
47
47
 
48
48
 
49
-
50
- def getPublicAttrs(obj):
51
- 'Return all public attributes (not methods or `_`-prefixed) on object.'
52
- return [k for k in dir(obj) if not k.startswith('_') and not callable(getattr(obj, k))]
53
-
54
49
  def PyobjColumns(obj):
55
50
  'Return columns for each public attribute on an object.'
56
- return [ColumnAttr(k, type=deduceType(getattr(obj, k))) for k in getPublicAttrs(obj)]
51
+ cols = []
52
+ for k in dir(obj):
53
+ coltype = anytype
54
+ try:
55
+ if k.startswith('_') or callable(getattr(obj, k)):
56
+ continue
57
+ coltype = deduceType(getattr(obj, k))
58
+ except AttributeError: #2631 attributes like formatted_help can raise AttributeError
59
+ pass
60
+ cols.append(ColumnAttr(k, type=coltype))
61
+ return cols
57
62
 
58
63
  def AttrColumns(attrnames):
59
64
  'Return column names for all elements of list `attrnames`.'
@@ -151,8 +156,11 @@ class ColumnSourceAttr(Column):
151
156
  'Use row as attribute name on sheet source'
152
157
  def calcValue(self, attrname):
153
158
  return getattr(self.sheet.source, attrname)
154
- def setValue(self, attrname, value):
155
- return setattr(self.sheet.source, attrname, value)
159
+ def setValue(self, attrname, value, setModified=True):
160
+ ret = setattr(self.sheet.source, attrname, value)
161
+ if setModified:
162
+ self.sheet.setModified()
163
+ return ret
156
164
 
157
165
  def docstring(obj, attr):
158
166
  v = getattr(obj, attr)
visidata/save.py CHANGED
@@ -85,6 +85,8 @@ def getDefaultSaveName(sheet):
85
85
  if hasattr(src, 'scheme') and src.scheme:
86
86
  return src.name + src.suffix
87
87
  if isinstance(src, Path):
88
+ if src.given == '-':
89
+ return f'stdin.{sheet.options.save_filetype}'
88
90
  if sheet.options.is_set('save_filetype', sheet):
89
91
  # if save_filetype is over-ridden from default, use it as the extension
90
92
  return str(src.with_suffix('')) + '.' + sheet.options.save_filetype
@@ -109,11 +111,17 @@ def saveCols(vd, cols):
109
111
 
110
112
  @VisiData.api
111
113
  def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
112
- 'Save all *vsheets* to *givenpath*.'
114
+ '''Save all *vsheets* to *givenpath*. Async.
115
+ Callers should be careful not to call reload() while saveSheets is still running.
116
+ Use vd.sync(saveSheets) to wait for the save to finish.'''
113
117
 
114
118
  if not vsheets: # blank tuple
115
119
  vd.warning('no sheets to save')
116
120
  return
121
+ if not givenpath.name:
122
+ vd.warning('no save path given')
123
+ return
124
+
117
125
  unloaded = [ vs for vs in vsheets if vs.rows is UNLOADED ]
118
126
  vd.sync(*vd.ensureLoaded(unloaded))
119
127
 
@@ -128,7 +136,7 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
128
136
  break
129
137
 
130
138
  if savefunc is None:
131
- vd.fail(f'no function to save as {filetype}')
139
+ vd.fail(f'no function to save as {", ".join(filetypes)}')
132
140
 
133
141
  if confirm_overwrite:
134
142
  vd.confirmOverwrite(givenpath)
visidata/selection.py CHANGED
@@ -20,9 +20,10 @@ def isSelected(self, row):
20
20
 
21
21
  @Sheet.api
22
22
  @asyncthread
23
- def toggle(self, rows):
23
+ def toggle(self, rows, add_undo=True):
24
24
  'Toggle selection of given *rows*. Async.'
25
- self.addUndoSelection()
25
+ if add_undo:
26
+ self.addUndoSelection()
26
27
  for r in Progress(rows, 'toggling', total=len(rows)):
27
28
  if self.isSelected(r): #1671
28
29
  self.unselectRow(r)
@@ -30,17 +31,24 @@ def toggle(self, rows):
30
31
  self.selectRow(r)
31
32
 
32
33
 
34
+ @Sheet.before
35
+ def beforeLoad(self):
36
+ self._selectedRows.clear()
37
+
38
+
33
39
  @Sheet.api
34
- def select_row(self, row):
40
+ def select_row(self, row, add_undo=True):
35
41
  'Add single *row* to set of selected rows.'
36
- self.addUndoSelection()
42
+ if add_undo:
43
+ self.addUndoSelection()
37
44
  self.selectRow(row)
38
45
 
39
46
 
40
47
  @Sheet.api
41
- def toggle_row(self, row):
48
+ def toggle_row(self, row, add_undo=True):
42
49
  'Toggle selection of given *row*.'
43
- self.addUndoSelection()
50
+ if add_undo:
51
+ self.addUndoSelection()
44
52
  if self.isSelected(row):
45
53
  self.unselectRow(row)
46
54
  else:
@@ -48,9 +56,10 @@ def toggle_row(self, row):
48
56
 
49
57
 
50
58
  @Sheet.api
51
- def unselect_row(self, row):
59
+ def unselect_row(self, row, add_undo=True):
52
60
  'Remove single *row* from set of selected rows.'
53
- self.addUndoSelection()
61
+ if add_undo:
62
+ self.addUndoSelection()
54
63
  self.unselectRow(row) or vd.warning('row not selected')
55
64
 
56
65
 
@@ -77,9 +86,10 @@ def clearSelected(self):
77
86
 
78
87
  @Sheet.api
79
88
  @asyncthread
80
- def select(self, rows, status=True, progress=True):
81
- "Add *rows* to set of selected rows. Async. Don't show progress if *progress* is False; don't show status if *status* is False."
82
- self.addUndoSelection()
89
+ def select(self, rows, status=True, progress=True, add_undo=True):
90
+ "Add *rows* to set of selected rows. Async. Don't show progress if *progress* is False; don't show status if *status* is False. If *add_undo* is False, do not add an undo selection function to the undo history; useful for lowering memory consumption when caller is changing a large batch of selects in one command."
91
+ if add_undo:
92
+ self.addUndoSelection()
83
93
  before = self.nSelectedRows
84
94
  if self.options.bulk_select_clear:
85
95
  self.clearSelected()
@@ -94,9 +104,10 @@ def select(self, rows, status=True, progress=True):
94
104
 
95
105
  @Sheet.api
96
106
  @asyncthread
97
- def unselect(self, rows, status=True, progress=True):
98
- "Remove *rows* from set of selected rows. Async. Don't show progress if *progress* is False; don't show status if *status* is False."
99
- self.addUndoSelection()
107
+ def unselect(self, rows, status=True, progress=True, add_undo=True):
108
+ "Remove *rows* from set of selected rows. Async. Don't show progress if *progress* is False; don't show status if *status* is False. If *add_undo* is False, do not add an undo unselection function to the undo history; useful for lowering memory consumption when caller is changing a large batch of selects in one command."
109
+ if add_undo:
110
+ self.addUndoSelection()
100
111
  before = self.nSelectedRows
101
112
  for r in (Progress(rows, 'unselecting') if progress else rows):
102
113
  self.unselectRow(r)
@@ -191,10 +202,10 @@ Sheet.addCommand('\\', 'unselect-col-regex', 'unselectByIdx(searchInputRegex("un
191
202
  Sheet.addCommand('g|', 'select-cols-regex', 'selectByIdx(searchInputRegex("select", columns="visibleCols"))', 'select rows matching regex in any visible column')
192
203
  Sheet.addCommand('g\\', 'unselect-cols-regex', 'unselectByIdx(searchInputRegex("unselect", columns="visibleCols"))', 'unselect rows matching regex in any visible column')
193
204
 
194
- Sheet.addCommand(',', 'select-equal-cell', 'select(gatherBy(lambda r,c=cursorCol,v=cursorDisplay: c.getDisplayValue(r) == v), progress=False)', 'select rows matching current cell in current column')
195
- Sheet.addCommand('g,', 'select-equal-row', 'select(gatherBy(lambda r,currow=cursorRow,vcols=visibleCols: all([c.getDisplayValue(r) == c.getDisplayValue(currow) for c in vcols])), progress=False)', 'select rows matching current row in all visible columns')
196
- Sheet.addCommand('z,', 'select-exact-cell', 'select(gatherBy(lambda r,c=cursorCol,v=cursorTypedValue: c.getTypedValue(r) == v), progress=False)', 'select rows matching current cell in current column')
197
- Sheet.addCommand('gz,', 'select-exact-row', 'select(gatherBy(lambda r,currow=cursorRow,vcols=visibleCols: all([c.getTypedValue(r) == c.getTypedValue(currow) for c in vcols])), progress=False)', 'select rows matching current row in all visible columns')
205
+ Sheet.addCommand(',', 'select-equal-cell', 'select(gatherBy(lambda r,c=cursorCol,v=cursorDisplay: c.getDisplayValue(r) == v), progress=False)', 'select rows matching current cell displayed value in current column')
206
+ Sheet.addCommand('g,', 'select-equal-row', 'select(gatherBy(lambda r,currow=cursorRow,vcols=visibleCols: all([c.getDisplayValue(r) == c.getDisplayValue(currow) for c in vcols])), progress=False)', 'select rows matching displayed values in current row in all visible columns')
207
+ Sheet.addCommand('z,', 'select-exact-cell', 'select(gatherBy(lambda r,c=cursorCol,v=cursorTypedValue: c.getTypedValue(r) == v), progress=False)', 'select rows matching current cell typed value in current column')
208
+ Sheet.addCommand('gz,', 'select-exact-row', 'select(gatherBy(lambda r,currow=cursorRow,vcols=visibleCols: all([c.getTypedValue(r) == c.getTypedValue(currow) for c in vcols])), progress=False)', 'select rows matching typed values in current row in all visible columns')
198
209
 
199
210
  Sheet.addCommand('z|', 'select-expr', 'expr=inputExpr("select by expr: "); select(gatherBy(lambda r, sheet=sheet, expr=expr, curcol=cursorCol: sheet.evalExpr(expr, r, curcol=curcol)), progress=False)', 'select rows matching Python expression in any visible column')
200
211
  Sheet.addCommand('z\\', 'unselect-expr', 'expr=inputExpr("unselect by expr: "); unselect(gatherBy(lambda r, sheet=sheet, expr=expr, curcol=cursorCol: sheet.evalExpr(expr, r, curcol=curcol)), progress=False)', 'unselect rows matching Python expression in any visible column')
visidata/settings.py CHANGED
@@ -446,13 +446,17 @@ def loadConfigAndPlugins(vd, args=AttrDict()):
446
446
  # autoload installed plugins first
447
447
  args_plugins_autoload = args.plugins_autoload if 'plugins_autoload' in args else True
448
448
  if not args.nothing and args_plugins_autoload and vd.options.plugins_autoload:
449
- from importlib_metadata import entry_points # a backport which supports < 3.8 https://github.com/pypa/twine/pull/732
449
+ from importlib.metadata import entry_points
450
+ eps_visidata = []
450
451
  try:
451
452
  eps = entry_points()
452
- eps_visidata = eps.select(group='visidata.plugins') if 'visidata.plugins' in eps.groups else []
453
+ vp = 'visidata.plugins'
454
+ if hasattr(eps, 'groups'): #Python >= 3.10
455
+ eps_visidata = eps.select(group=vp)
456
+ else: #Python < 3.10
457
+ eps_visidata = eps.get(vp, [])
453
458
  except Exception as e:
454
- eps_visidata = []
455
- vd.warning('plugin autoload failed; see issue #1529')
459
+ vd.warning(f'plugin autoload failed; see issue #1529: {e}')
456
460
 
457
461
  for ep in eps_visidata:
458
462
  try:
@@ -550,7 +554,7 @@ def setPersistentOptions(vd, **kwargs):
550
554
  fp.write(f'options.{optname}={repr(optval)}\n')
551
555
 
552
556
 
553
- vd.option('visidata_dir', '~/.visidata/', 'directory to load and store additional files', sheettype=None)
557
+ vd.option('visidata_dir', user_config_dir('visidata'), 'directory to load and store additional files', sheettype=None)
554
558
 
555
559
  BaseSheet.bindkey('^M', '^J') # for windows ENTER
556
560