File: bufutils.sl

package info (click to toggle)
jed-extra 2.5.6-2
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 2,564 kB
  • sloc: makefile: 75; ruby: 43; sed: 38; sh: 31
file content (912 lines) | stat: -rw-r--r-- 30,437 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
% bufutils.sl  Tools for buffer and windows handling
%
% Copyright (c) 2006 Guenter Milde (milde users.sf.net)
% Released under the terms% of the GNU General Public License (version 2 or later).
%
% Versions
% --------
% 
%            1.0   first public version
%            1.1   bugfix: restore_buffer now resets the "changed on disk" flag
%            1.2   new: "blocal_hooks"
%            1.2.2 "outsourcing" of window_set_rows (hint by Thomas Clausen)
%	     1.3   moved most often used programming helpers to sl_utils.sl
%	           new: (key_prefix="") argument to rebind, rebind_reserved
%	                (hint and bugfix Paul Boekholt)
%	           rework of popup_buffer
%	             - do not reuse popups
%	             - reload old buffer when closing (Paul Boekholt)
%	     1.4   new: help_message(): Give mode-dependend help message
%	                arrayread_file(name): read file to array (P. Boekholt)
%	           changed: close_buffer calls blocal_hook
%	                    popup_buffer uses this
%	     	   moved next_buffer() to cuamisc.sl
%	     	   renamed restore_buffer() to reload_buffer() 
%	     1.4.1 bugfix popup_buffer/close_buffer/popup_close_buffer_hook
%	           bugfix reload_buffer()
% 2004-03-17 1.5   new function bufsubfile() (save region|buffer to a tmp-file)
% 2004-03-23 1.5.1 bugfix: spurious ";" in delete_temp_files()
%	     1.6   moved untab_buffer() from recode.sl here
%	     1.6.1 small bugfix in bufsubfile()
% 2005-03-24 1.7   bufsubfile() always writes a temp-file (hint P. Boekholt)
%	  	   (-> more consistency, no asking)
%	  	   removed custom var Bufsubfile_Save_Ask)
%	  	   bufsubfile() takes an optional argument `base`
% 2005-03-31 1.7.1 made slang-2 proof: A[[0:-2]] --> A[[:-2]]
% 2005-04-01 1.8   fast strread_file() (Paul Boekholt)
% 2005-04-08 1.8.1 made preparse-proof
% 	     	   "#if (_slang_version < 2000)" cannot be preparsed
% 	     1.8.2 bugfix in bufsubfile(): use path_basename() of whatbuf()
% 	           (important, if the buffer name is  e.g.
% 	           "http://jedmodes.sf.net")
% 2005-10-13 1.8.3 bugfix reload_buffer(): reset the changed on disk argument
%                  permanently
% 2005-11-08 1.8.4 simplified reload_buffer() again, as jed 0.99.17.135
% 	           will reset the buffer's ctime field if the changed-on-disk
% 	           flag is reset
% 2005-11-21 1.8.5 removed public from popup_buffer() definition
% 2005-11-25 1.8.6 bugfix in close_buffer():
%                  switch back to current buffer if closing a different one
% 2006-01-11 1.9   bugfix in close_and_insert_word and close_and_replace_word
%                  (report Paul Boekholt)
%                  revised approach to "backswitching" after a buffer is closed
% 2006-05-29 1.10  run_local_hook() tries mode_get_mode_info(hook) before
%                  get_blocal(hook)
% 	     	   custom var Jed_Temp_Dir renamed to Jed_Tmp_Directory
% 	     	   (which is new in site.sl since 0.99.17.165)
% 2006-06-19 1.11  fit_window(): abort if there is only one open window
% 2006-10-04 1.12  bufsubfile() uses make_tmp_file(), documentation update
% 2006-10-23 1.13  bugfix in bufsubfile() by Paul Boekholt
% 	     	   "\\/ " specifies a character class '/'
% 2006-11-23 1.13.1 bugfix in reload_buffer(): reset "changed on disk" flag
% 	     	    before erasing buffer (to prevent asking befor edit)
% 2007-04-18 1.14   new function run_local_function() (used in help.sl)
% 	     	    example for "Fit Window" menu entry
% 	     	    TODO: (should this become an INITIALIZATION block?)
% 2007-05-11 1.15   removed non-standard fun latex_compose() from documentation
% 		    run_local_function(): try mode-info also with 
% 		    normalized_modename()
% 		    use mode_info instead of global var for help_message()
% 2008-01-11 1.16   reload_buffer(): insert disk version, delete content later
% 	     	                     preventing an empty buffer after undo(),
%		    Minor code and doc edits (cleanup).
% 2008-01-21 1.17   fit_window(): recenter if window contains whole buffer
% 2008-05-05 1.17.1 reload_buffer(): backup buffer (if modified and backups
% 	     	    are not disabled) before re-loading)
% 2008-06-18 1.18   New function get_local_var()
% 2009-10-05 1.19   New function reopen_file()
% 2009-12-08 1.19.1 adapt to new require() syntax in Jed 0.99.19

provide("bufutils");

% Requirements 
% ------------

% Jed >= 0.99.17.135 for proper working of reload_buffer()
% but at least Jed >= 0.99.16 (mode_set_mode_info() with arbitrary fields)

% standard (but not loaded by default):
#if (_jed_version > 9918)
  require("keydefs", "Global");
#else
  require("keydefs"); % symbolic constants for many function and arrow keys
#endif

% jedmodes.sf.net modes:
autoload("get_blocal", "sl_utils");
autoload("push_defaults", "sl_utils");
autoload("run_function", "sl_utils");
autoload("get_word", "txtutils");
autoload("mark_word", "txtutils");

% Functions 
% ---------

% Convert the modename to a canonic form (the donwcased first part)
% This can be used for mode-dependend help, variables, ...
define normalized_modename() % (mode=get_mode_name())
{
   variable mode = push_defaults(get_mode_name, _NARGS);
   mode = extract_element(mode, 0, ' ');
   if (mode == "")
     mode = "no";
   return strlow (mode);
}

% Local variables, functions, and hooks
% -------------------------------------
%
% Tools for the definition and use of mode- or buffer local settings -- just like
% the indent_hook or the newline_and_indent_hook jed already provides. Extend
% this idea to additional settings that can be set by a mode and used by another.
% Allows customisation to be split in a "language" mode that provides
% functionality and an "emulation" mode that does the keybinding.
%
% Implementation is done via blocal vars and the mode_*_mode_info functions.
% 
% A hook can be defined either a pointer (reference) to a function or the function
% name as string.


%!%+
%\function{get_local_var}
%\synopsis{Return value of either buffer-local or mode-local variable.}
%\usage{get_local_var(name, default=NULL)}
%\description
%  Return the value of variable/setting \var{name}, either
%    * buffer-local (blocal) variable \var{name},
%    * mode-local (\sfun{with mode_get_mode_info}), or
%    * \var{default} (defaulting to \var{NULL})
%\notes
%  The value of a buffer- or mode-local setting is tested against
%  \var{NULL}, so e.g. a buffer-local variable with value NULL is treated 
%  like a non-existing blocal variable.
%\seealso{get_blocal_var, mode_get_mode_info, run_local_function, run_local_hook}
%!%-
define get_local_var() % (name, default=NULL)
{
   variable name, default;
   (name, default) = push_defaults( , NULL, _NARGS);
   variable value = get_blocal_var(name, NULL);
   if (value == NULL) {
      value = mode_get_mode_info(name);
      if (value == NULL) {
	 value = mode_get_mode_info(normalized_modename(), name);
	 if (value == NULL) {
	    value = default;
	 }
      }
   }
   return value;
}

%!%+
%\function{run_local_function}
%\synopsis{Run a local function if it exists, return if fun is found}
%\usage{Int_Type run_local_function(fun, [args])}
%\description
%  Similar to \sfun{run_local_hook}, but return an Integer indicating
%  whether a local function was found.
%\seealso{run_local_hook, run_function}
%!%-
define run_local_function() % (fun, [args])
{
   variable args = __pop_args(_NARGS-1);
   variable fun = ();

   variable lfun = get_local_var(fun);
   if (lfun == NULL)
     lfun = sprintf("%s_%s", normalized_modename(), fun);
   return run_function(lfun, __push_args(args));
}

%!%+
%\function{run_local_hook}
%\synopsis{Run a local hook if it exists}
%\usage{ Void run_local_hook(String hook, [args])}
%\description
%  The hook is looked for in the following places:
%
%    * the blocal variable \var{hook},
%    * the mode info field \var{hook}, or
%    * a function with name <modename>_\var{hook}
%      [i.e. sprintf("%s_%s", normalized_modename(), hook)]
%
%  and can be defined with one of
%#v+
%   define_blocal_var("<hook>", &<function_name>);
%   define_blocal_var("<hook>", "<function_name>");
%   mode_set_mode_info("<hook>", "<function_name>");
%   mode_set_mode_info("<modename>", "<hook>", "<function_name>");
%   define <modename>_<hook>() { <code> }
%#v-
%  This way a mode can set a mode- or buffer-dependent function to a common
%  keybinding.
%\example
% Set up a key to do a default action on a buffer ("run it"):
%#v+
%   define run_buffer() { run_local_hook("run_buffer_hook"); }
%   setkey("run_buffer", "^[^M");    % Alt-Return
%   mode_set_mode_info("SLang", "run_buffer_hook", "evalbuffer");
%   mode_set_mode_info("python", "run_buffer_hook", "py_exec");
%#v-
%\seealso{runhooks, run_local_function, get_blocal, run_buffer}
%!%-
define run_local_hook() % (hook, [args])
{
   variable args = __pop_args(_NARGS);
   % call run_local_function and discard the return value
   () = run_local_function(__push_args(args));
}

% deprecated, use run_local_hook instead
define run_blocal_hook() % (hook, [args])
{
   variable args = __pop_args(_NARGS-1);
   variable hook = ();
   () = run_function(get_blocal(hook, NULL), __push_args(args));
}

%!%+
%\function{run_buffer}
%\synopsis{"Run" the current buffer}
%\usage{Void run_buffer()}
%\description
%  "Run" the current buffer. The actual function performed is defined by
%  the local "run_buffer_hook" (see \sfun{run_local_hook}).
%\example
%  Some modes set the "run_buffer_hook" by themself, for others you can use
%  \sfun{mode_set_mode_info} (since Jed 0.99.17), e.g.
%#v+
%   mode_set_mode_info("SLang", "run_buffer_hook", "evalbuffer");
%#v-
%  or using mode_hooks (this variant is also proof for Jed <= 0.99.16)
%#v+
%   define calc_mode_hook ()
%   {
%      define_blocal_var("run_buffer_hook", "calc_make_calculation");
%      set_buffer_undo(1);
%   }
%#v-
%\seealso{run_local_hook, evalbuf}
%!%-
public define run_buffer()
{
   run_local_hook("run_buffer_hook");
}

% Set the mode-dependend string with help (e.g. on keybindings)
define set_help_message() % (str, mode=get_mode_name())
{
   variable str, mode; % optional argument
   (str, mode) = push_defaults(get_mode_name(), _NARGS-1);
   mode_set_mode_info(mode, "help_message", str);
}

% Show a mode-dependend string with help (e.g. on keybindings)
define help_message()
{
   variable str = get_local_var("help_message");
   if (str == NULL)
     str = sprintf("no help available for '%s' mode", get_mode_name());
   message(str);
}

% --- window operations ----------------------------------------------

%!%+
%\function{window_set_rows}
%\synopsis{Make the current window \var{n} rows big}
%\usage{window_set_rows(Int n)}
%\usage{window_set_rows(Double_Type n)}
%\description
% Resizes the current window:
%   If \var{n} is of Double_Type (e.g. 0.5), the window is rezized to
%   this fraction of the screen.
%   If there is only one window, a new window is created.
%   If \var{n} is zero, the window is deleted
%   If \var{n} is negative, the window is reduced by \var{n} lines.
%   (Use loop(n) enlargewin(); to get relative enlargement.)
%\notes
% If there are more than two windows open, the function might not work as
% desired.
%\seealso{fit_window, enlargewin, splitwindow, onewindow}
%!%-
define window_set_rows(n)
{
   % convert n from fraction to absolute if Double_Type:
   if (typeof(n) == Double_Type)
     n = int((SCREEN_HEIGHT - TOP_WINDOW_ROW - 2) * n);
   if (n == 0)
     	call("delete_window");
   if (n < 0)
       n += window_info('r');
   if (nwindows() - MINIBUFFER_ACTIVE == 1)
     splitwindow();
   if (n >= SCREEN_HEIGHT - TOP_WINDOW_ROW - 2)
     onewindow();
   variable misfit = n - window_info('r');
   if (misfit > 0) { % window too small
      loop(misfit)
	enlargewin();
   }
   if (misfit < 0) { % window too large
      otherwindow();
      loop(-misfit)
	enlargewin ();
      loop(nwindows() - 1)
	otherwindow();
   }
   if (eobp)
     recenter(n);
}

%!%+
%\function{fit_window}
%\synopsis{Fit the window size to the lenght of the buffer}
%\usage{fit_window (max_rows=1.0)}
%\description
% If there is more than one window open, the size of the current window is
% adapted to the length of the buffer it contains. The optional argument
% \var{max_rows} gives the upper limit for the window size, either as
% proportion of the total space (\var{Double_Type}) or as number of lines
% (\var{Integer_Type}). The default max_rows=1.0 means no limit, max_rows=0
% means: don't fit.
%\example
% To add a "Fit Window" entry to the "Windows" menu, you can define (or amend)
% a popup-hook e.g.
%#v+
%   autoload("fit_window", "bufutils");
%   define fit_window_load_popup_hook(menubar)
%   {
%      menu_insert_item(4, "Global.W&indows", "&Fit Window", "fit_window");
%   }
%   append_to_hook("load_popup_hooks", &fit_window_load_popup_hook);
%#v-
%\seealso{window_set_rows, enlargewin, popup_buffer}
%!%-
public define fit_window () % fit_window(max_rows = 1.0)
{
   variable max_rows = push_defaults(1.0, _NARGS);
   % abort, if there is only one window (or max_rows is 0)
   if (nwindows() - MINIBUFFER_ACTIVE == 1 or max_rows == 0)
     return;
   % convert max_rows from fraction to absolute if Double_Type:
   if (typeof(max_rows) == Double_Type)
     max_rows = int((SCREEN_HEIGHT - TOP_WINDOW_ROW - 1) * max_rows);
   % get the number of lines in the current buffer
   push_spot();
   eob;
   variable wanted_rows = what_line;
   pop_spot();
   % limit to max_rows
   if (wanted_rows > max_rows)
     wanted_rows = max_rows;
   % fit window
   window_set_rows(wanted_rows);
   % if window contains whole buffer, put last line at bottom line
   if (wanted_rows <= max_rows)
      recenter(what_line());
}

% --- closing the buffer -------------------------------------------------

%!%+
%\function{close_buffer}
%\synopsis{Close the current (or given) buffer}
%\usage{ Void close_buffer(buf = whatbuf())}
%\description
%   Close the current (or given) buffer.
%   Run the blocal "close_buffer_hook"
%\seealso{delbuf, close_window, popup_buffer, set_blocal_var}
%!%-
public define close_buffer() % (buf = whatbuf())
{
   variable buf = push_defaults(whatbuf(), _NARGS);
   variable currbuf = whatbuf();

   sw2buf(buf);
   run_local_hook("close_buffer_hook", buf);
   delbuf(buf);
   % make sure to stay in the current buffer after closing a different one
   if (currbuf != buf)
     sw2buf(currbuf);
}

% close buffer in second window if there are two windows
define close_other_buffer ()
{
   if (nwindows () - MINIBUFFER_ACTIVE > 1)
     {
	otherwindow();
	close_buffer();
     }
}

%!%+
%\function{close_and_insert_word}
%\synopsis{Close buffer, insert current word in calling buffer}
%\usage{close_and_insert_word()}
%\description
%  Close buffer, insert current word in the buffer indicated by
%  the buffer-local ("blocal") variable "calling_buffer".
%\notes
%  The \sfun{popup_buffer} function automatically records the calling
%  buffer.
%\seealso{close_and_replace_word, get_word, popup_buffer, close_buffer}
%!%-
define close_and_insert_word()
{
   variable word = get_word(),
   calling_buf = get_blocal("calling_buf", "");
   close_buffer();
   if (bufferp(calling_buf))
     sw2buf(calling_buf);
   else
     verror("calling buffer \"%s\" does not exist", calling_buf);
   insert(word);
}

%!%+
%\function{close_and_replace_word}
%\synopsis{Close buffer, replace current word in calling buffer}
%\usage{close_and_replace_word()}
%\description
%  Close buffer, insert current word into the buffer indicated by the blocal
%  variable "calling_buffer" replacing the current word (or visible region)
%  there.
%\notes
%  The \sfun{popup_buffer} function automatically records the calling
%  buffer.
%\seealso{close_and_insert_word, popup_buffer, close_buffer, get_blocal}
%!%-
define close_and_replace_word()
{
   variable word = get_word(),
   calling_buf = get_blocal("calling_buf", "");
   close_buffer();
   if (bufferp(calling_buf))
     sw2buf(calling_buf);
   else
     verror("calling buffer \"%s\" does not exist", calling_buf);
   !if (is_visible_mark)
     mark_word;
   del_region();

   insert(word);
}

% go to the buffer, if it is already visible (maybe in another window)
% open it in the current window otherwise
define go2buf(buf)
{
   if(buffer_visible(buf))
     pop2buf(buf);   % open in other window
   else
     sw2buf(buf);    % open in current window
}

% Popup Buffer
% ------------

custom_variable("Max_Popup_Size", 0.7);       % max size of one popup window

% TODO: do we want support for more than 1 popup window in parrallel 
% (i.e. more than 2 open windows):
% custom_variable("Popup_max_popups", 2);        % max number of popup windows
% custom_variable("Popup_max_total_size", 0.7);  % max size of all popup windows

% close popup window, if the buffer is visible and resizable
define popup_close_buffer_hook(buf)
{
   % abort if buffer is not attached to a window
   !if (buffer_visible(buf))
     return;

   variable replaced_buf = get_blocal("replaced_buf", "");
   variable calling_buf = get_blocal("calling_buf", "");

   % resizable popup window: close it
   if (get_blocal_var("is_popup") != 0)
     call("delete_window");
   else
     {
	if (bufferp(replaced_buf))
	  {
	     sw2buf(replaced_buf);
	     fit_window(get_blocal("is_popup", 0)); % resize popup window
	  }
     }
   % Return to the minibuffer line, if opened from there
   if (calling_buf == " <mini>" and MINIBUFFER_ACTIVE)
     loop (nwindows())
       {
          otherwindow();
          if (whatbuf() == " <mini>")
            break;
       }
   % Return to calling buffer (if it is visible, it might be annoying if
   % closing a help buffer pops up some buffer no longer in active use).
   else if (buffer_visible(calling_buf))
     sw2buf(calling_buf);
}

%!%+
%\function{popup_buffer}
%\synopsis{Open a "popup" buffer}
%\usage{popup_buffer(buf=whatbuf(), max_rows=Max_Popup_Size)}
%\description
% The "popup" buffer opens in a second window (using pop2buf).
% Closing with close_buffer closes the popup window (if new)
% or restores the previous buffer (if reused).
%
% The blocal variable "is_popup" marks the buffer as "popup".
% It contains the upper limit when fitting the window or 0 if the window
% should not be resized.
%
%\example
%  Open a popup window and fit (if applicable) after inserting stuff:
%#v+
%        popup_buffer(buf);
%        insert("hello world");
%        % insert_file("hello.txt");
%        fit_window(get_blocal("is_popup", 0));
%#v-
%
%\seealso{setbuf, sw2buf, close_buffer, fit_window, delete_window}
%!%-
define popup_buffer() % (buf=whatbuf(), max_rows = Max_Popup_Size)
{
   % get arguments
   variable buf, max_rows;
   (buf, max_rows) = push_defaults(whatbuf(), Max_Popup_Size, _NARGS);

   variable replaced_buf, calling_buf = whatbuf();
   variable open_windows = nwindows() - MINIBUFFER_ACTIVE; % before opening new
   % Open/go_to the buffer, store the replaced buffers name
   replaced_buf = pop2buf_whatbuf(buf);
   % The buffer is displayed
   %  a) in a new window or reusing a popup window
   %  -> we can savely fit the window and close it when closing the buffer
   % or
   %  b) in an existing "permanent" (non-popup) window.
   %  -> set max_rows to 0 to prevent meddling in existing split schemes.
   if (open_windows > 1)
     {
	sw2buf(replaced_buf);
	if (get_blocal("is_popup", 0) == 0)
	  max_rows = 0;
	sw2buf(buf);
     }
   define_blocal_var("is_popup", max_rows);
   define_blocal_var("close_buffer_hook", &popup_close_buffer_hook);
   define_blocal_var("replaced_buf", replaced_buf);
   if (buf != calling_buf)
     define_blocal_var("calling_buf", calling_buf);
}

% --- push_keymap/pop_keymap --- (turn on/off a minor mode) ----------------
%
% see also push_mode/pop_mode from pushmode.sl

private variable _stack_name = "keymap_stack";

% temporarily push the keymap
define push_keymap(new_keymap)
{
   !if (blocal_var_exists(_stack_name))
     define_blocal_var(_stack_name, {});
   variable keymaps = get_blocal_var(_stack_name);
   variable old_keymap = what_keymap();
   variable mode, flag;
   (mode, flag) = what_mode();
   
   use_keymap(new_keymap);
   % push the old keymap and mode name on blocal stack
   list_append(keymaps, mode);
   list_append(keymaps, old_keymap);
   % append the new keymap to the modename
   set_mode(sprintf("%s (%s)", mode, new_keymap), flag);
   %Test show("keymap stack is:", get_blocal(_stack_name));
   %Test show("current keymap is:", what_keymap());
}

define pop_keymap()
{
   variable keymaps = get_blocal_var(_stack_name);
   variable old_keymap = list_pop(keymaps, -1);
   variable old_mode = list_pop(keymaps, -1);
   variable flag;
   (, flag) = what_mode();
   
   use_keymap(old_keymap);
   set_mode(old_mode, flag);
   %Test show("keymap stack is:", get_blocal(_stack_name));
   %Test	show("current keymap is:", what_keymap());
}

%!%+
%\function{rebind}
%\synopsis{Rebind all keys bound to \var{old_fun} to \var{new_fun}.}
%\usage{rebind(old_fun, new_fun, keymap=what_keymap(), prefix="")}
%\description
% The function acts on the local keymap (if not told otherwise by the
% \var{keymap} argument. It scans for all bindings to \var{old_fun} with
% \sfun{which_key} and sets them to \var{new_fun}.
%\example
%  The email mode (email.sl) uses rebind to bind the mode-specific formatting
%  function to the key(s) used for format_paragraph:
%#v+
%  rebind("format_paragraph", "email_reformat", mode);
%#v-
%\notes
%  If the optional argument \var{prefix} is not empty, the prefix will be
%  prepended to the key to bind to. Use this to create "maps" of bindings
%  that reflect the users normal binding, e.g. with \var{_Reserved_Key_Prefix}
%  (this is what \sfun{rebind_reserved} does).
%\seealso{setkey, local_setkey, definekey, definekey_reserved}
%!%-
define rebind() % (old_fun, new_fun, keymap=what_keymap(), prefix="")
{
   variable old_fun, new_fun, keymap, prefix;
   (old_fun, new_fun, keymap, prefix) =
     push_defaults( , , what_keymap(),"", _NARGS);

   variable key;
   loop (which_key(old_fun))
   {
      key = ();
      definekey(new_fun, prefix + key, keymap);
   }
}

%!%+
%\function{rebind_reserved}
%\synopsis{Rebind a function prepending the \var{_Reserved_Key_Prefix}}
%\usage{ rebind_reserved(old_fun, new_fun, keymap)}
%\description
% Call \sfun{rebind} with \var{prefix} set to \var{_Reserved_Key_Prefix}.
%\notes
% The action is more a remodelling than a rebinding, the name should reflect
% the close relation to the \sfun{rebind} function.
%\seealso{rebind, definekey_reserved, setkey_reserved}
%!%-
define rebind_reserved(old_fun, new_fun, keymap)
{
   rebind(old_fun, new_fun, keymap, _Reserved_Key_Prefix);
}

% --- some more buffer related helpers ----------------------------------

%!%+
%\function{buffer_dirname}
%\synopsis{Return the directory associated with the buffer}
%\usage{Str buffer_dirname(buf=whatbuf())}
%\description
%   Return the directory associated with the buffer
%\seealso{getbuf_info, buffer_filename}
%!%-
define buffer_dirname()
{
   variable dir, args = __pop_args(_NARGS);
   ( , dir, , ) = getbuf_info(__push_args(args));
   return dir;
}

%!%+
%\function{arrayread_file}
%\synopsis{Read a file and return it as array of lines.}
%\usage{Array[String] arrayread_file(name)}
%\description
%   Read a file and return it as a String_Type array of lines.
%   Newlines are preserved.
%\notes
% To get rid of the newlines, you can do
%#v+
%  result = array_map(String_Type, &strtrim_end, arrayread_file(name), "\n");
%#v-
%\seealso{strread_file, fgetslines}
%!%-
define arrayread_file(name)
{
   variable fp = fopen (name, "r");
   if (fp == NULL) verror ("File %s not found", name);
   fgetslines(fp);
   () = fclose (fp);
}

%!%+
%\function{strread_file}
%\synopsis{Read a file and return as (binary) string}
%\usage{BString strread_file(String name)}
%\description
%   Read a file and return as string (\var{BString_Type}).
%\notes
%   If the file size exceeds the internal limit (currently 5MB),
%   an error is returned.
%\seealso{arrayread_file, find_file, fread, fread_bytes}
%!%-
define strread_file(name)
{
   % return strjoin(arrayread_file(name), "");
   variable size_limit = 5000000; % this should be a custom var
   variable fp = fopen(name, "r"), str;
   if (fp == NULL)
     verror ("Failed to open \"%s\"", name);
#ifnexists _slang_utf8_ok   % (_slang_version < 2000)
   if (-1 == fread(&str, Char_Type, size_limit, fp))
#else
   if (-1 == fread_bytes(&str, size_limit, fp))
#endif
     error("could not read file");
   !if (feof(fp))
     verror("file exceedes limit (%d bytes)", size_limit);
   return str;
}

%!%+
%\function{reload_buffer}
%\synopsis{Restore (or update) a buffer to the version on disk}
%\usage{reload_buffer()}
%\description
%  Replace the buffer contents with the content of the associated file.
%  This will restore the last saved version or update (if the file changed
%  on disk).
%\seealso{insert_file, find_file, write_buffer, make_backup_filename}
%!%-
public define reload_buffer()
{
   variable file, dir, name, flags;
   (file, dir, name, flags) = getbuf_info();
   variable col = what_column(), line = what_line();

   % save to backup file 
   if (flags & 0x001            % buffer modified
       and not(flags & 0x100))  % backups (not) disabled
     {
	% use write_region_to_file() to prevent attaching buffer to backup file
	mark_buffer();
	() = write_region_to_file(make_backup_filename(dir, file));
     }
   
   % Variant: only update (make this an option?)
   % !if (flags & 4)
   %   return;
     
   % reset the changed-on-disk flag
   setbuf_info(file, dir, name, flags & ~0x004);
   
   erase_buffer(whatbuf());
   () = insert_file(path_concat(dir, file));
   
   goto_line(line);
   goto_column_best_try(col);
   set_buffer_modified_flag(0);
}

%!%+
%\function{reopen_file}
%\synopsis{Re-open file \var{file}.}
%\usage{reopen_file(file)}
%\description
% In contrast to \sfun{reload_buffer}, \sfun{reopen_file} takes a 
% (full) filename as argument.
% 
% To prevent questions about changed versions on disk, it avoids switching
% to the buffer. Instead, it closes the buffer and re-loads the file with
% find_file(). 
% 
% Does nothing if file is up-to-date or not attached to any buffer.
%\seealso{}
%!%-
define reopen_file(file)
{
   % Put the list of buffers in an array instead of looping over
   % buffer_list(). This way leftovers after a `break` or `return` are 
   % automatically removed from the stack.
   variable buffers = [buffer_list(), pop];    
   variable buf, dir, f, flags;
   foreach buf (buffers) {
      (f, dir, ,flags) = getbuf_info(buf);
      if (dir + f == file and  flags & 4) { % file that changed on disk
	 delbuf(buf);
	 () = find_file(file);
	 % try to restore the point position from the recent files cache
	 call_function("recent_file_goto_point");   
	 break;
      }
   }
}

% ------- Write the region to a file and return its name. -----------------

% Directory for temporary files

% Backwards compatibility to earlier versions of bufutils.sl
#ifexists Jed_Temp_Dir
custom_variable("Jed_Tmp_Directory", Jed_Temp_Dir);
#endif

custom_variable("Jed_Tmp_Directory", getenv("TEMP"));
if (Jed_Tmp_Directory == NULL)
  Jed_Tmp_Directory = getenv("TMP");
if (Jed_Tmp_Directory == NULL)
  Jed_Tmp_Directory = "/tmp";

% list of files to delete at exit
% (defined with custom_variable, so a reevaluation of bufutils will not
%  delete the existing list.)
 custom_variable("Bufsubfile_Tmp_Files", "");

% cleanup at exit
static define delete_temp_files()
{
   variable file;
   foreach file (strchop(strtrim_beg(Bufsubfile_Tmp_Files), '\n', 0))
     if (file_status(file) == 1)
       delete_file(file);
   return 1;
}
add_to_hook("_jed_exit_hooks", &delete_temp_files);

%!%+
%\function{bufsubfile}
%\synopsis{Write region|buffer to a temporary file and return its name.}
%\usage{String = bufsubfile(delete=0, base=NULL)}
%\description
%   Write the region to a temporary file. If no visible region is defined,
%   write the whole buffer.
%
%   If \var{base} is not absolute, the file is written to the \var{Jed_Tmp_Directory}.
%   If \var{base} == NULL (default), the buffer-name is taken as basename
%   If \var{delete} != 0, delete the region|buffer after writing.
%
%   Return the full filename.
%
%   The temporary file will be deleted at exit of jed (if the calling
%   function doesnot delete it earlier).
%\notes
%   bufsubfile() enables shell commands working on files
%   to act on the current buffer and return the command output.
%    * run_shell_cmd() returns output but doesnot take input from jed,
%    * pipe_region() only takes input but outputs to stdout, but
%    * shell_cmd_on_region() uses bufsubfile() and run_shell_cmd() for
%      bidirectioal interaction
%   As some commands expect a certain file extension, the extension of
%   \var{base} is added to the temporary file's name.
%\seealso{make_tmp_file, is_visible_mark, push_visible_mark,
%\seealso{run_shell_cmd, shell_cmd_on_region, filter_region}
%!%-
define bufsubfile() % (delete=0, base=NULL)
{
   variable delete, base, filename, extension;
   (delete, base) = push_defaults(0, NULL, _NARGS);
   push_spot ();

   !if (is_visible_mark)
     mark_buffer();
   if (delete)
     () = dupmark();
   % create a unique filename (keeping the extension)
   if (base == NULL)
     base = str_delete_chars(path_basename(whatbuf()), "*+<>:/ \\");
   extension = path_extname(base);
   base = path_sans_extname(base);
   do
     {
        filename = strcat(make_tmp_file(base), extension);
     }
   while (file_status(filename));

   % write region/buffer to temporary input file
   () = write_region_to_file(filename);
   if (delete)
     del_region();
   % delete the file at exit
   Bufsubfile_Tmp_Files += "\n" + filename;
   pop_spot();

   % show("bufsubfile:", filename, Bufsubfile_Tmp_Files);
   return filename;
}

%!%+
%\function{untab_buffer}
%\synopsis{Untab the whole buffer}
%\usage{Void untab_buffer()}
%\description
%  Convert all hard tabs ("\\t") in the current buffer into spaces. The
%  buffer-local value of \var{TAB} determines how many spaces are used for the
%  substitution.
%\notes
%  Whether hard Tabs will be used for editing is defined by the
%  global variable \var{USE_TABS} and the buffer-local variable \var{TAB}.
%\seealso{untab}
%!%-
public define untab_buffer ()
{
   push_spot();
   mark_buffer();
   untab ();
   pop_spot();
}