blob: eab321bd92f5af735fa9d1a501f8623e9d4cab12 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * Tcl extensions by Ingo Wilken <Ingo.Wilken@informatik.uni-oldenburg.de>
12 * Last modification: Wed May 10 21:28:44 CEST 2000
13 * Requires Tcl 8.0 or higher.
14 *
15 * Variables:
16 * ::vim::current(buffer) # Name of buffer command for current buffer.
17 * ::vim::current(window) # Name of window command for current window.
18 * ::vim::range(start) # Start of current range (line number).
19 * ::vim::range(end) # End of current range (line number).
20 * ::vim::lbase # Start of line/column numbers (1 or 0).
21 *
22 * Commands:
23 * ::vim::command {cmd} # Execute ex command {cmd}.
24 * ::vim::option {opt} [val] # Get/Set option {opt}.
25 * ::vim::expr {expr} # Evaluate {expr} using vim's evaluator.
26 * ::vim::beep # Guess.
27 *
28 * set buf [::vim::buffer {n}] # Create Tcl command for buffer N.
29 * set bl [::vim::buffer list] # Get list of Tcl commands of all buffers.
30 * ::vim::buffer exists {n} # True if buffer {n} exists.
31 *
32 * set wl [::vim::window list] # Get list of Tcl commands of all windows.
33 *
34 * set n [$win height] # Report window height.
35 * $win height {n} # Set window height to {n}.
36 * array set pos [$win cursor] # Get cursor position.
37 * $win cursor {row} {col} # Set cursor position.
38 * $win cursor pos # Set cursor position from array var "pos"
39 * $win delcmd {cmd} # Register callback command for closed window.
40 * $win option {opt} [val] # Get/Set vim option in context of $win.
41 * $win command {cmd} # Execute ex command in context of $win.
42 * $win expr {expr} # Evaluate vim expression in context of $win.
43 * set buf [$win buffer] # Create Tcl command for window's buffer.
44 *
45 * $buf name # Reports file name in buffer.
46 * $buf number # Reports buffer number.
47 * set l [$buf get {n}] # Get buffer line {n} as a string.
48 * set L [$buf get {n} {m}] # Get lines {n} through {m} as a list.
49 * $buf count # Reports number of lines in buffer.
50 * $buf last # Reports number of last line in buffer.
51 * $buf delete {n} # Delete line {n}.
52 * $buf delete {n} {m} # Delete lines {n} through {m}.
53 * $buf set {n} {l} # Set line {n} to string {l}.
54 * $buf set {n} {m} {L} # Set lines {n} through {m} from list {L}.
55 * # Delete/inserts lines as appropriate.
56 * $buf option {opt} [val] # Get/Set vim option in context of $buf.
57 * $buf command {cmd} # Execute ex command in context of $buf
58 * $buf expr {cmd} # Evaluate vim expression in context of $buf.
59 * array set pos [$buf mark {m}] # Get position of mark.
60 * $buf append {n} {str} # Append string {str} to buffer,after line {n}.
61 * $buf insert {n} {str} # Insert string {str} in buffer as line {n}.
62 * $buf delcmd {cmd} # Register callback command for deleted buffer.
63 * set wl [$buf windows] # Get list of Tcl commands for all windows of
64 * # this buffer.
65TODO:
66 * ::vim::buffer new # create new buffer + Tcl command
67 */
68
69#include "vim.h"
Bram Moolenaar2ab2e862019-12-04 21:24:53 +010070#undef EXTERN // tcl.h defines it too
Bram Moolenaar071d4272004-06-13 20:20:40 +000071
72#ifdef DYNAMIC_TCL
Bram Moolenaar2ab2e862019-12-04 21:24:53 +010073# define USE_TCL_STUBS // use tcl's stubs mechanism
Bram Moolenaar071d4272004-06-13 20:20:40 +000074#endif
75
76#include <tcl.h>
Bram Moolenaar071d4272004-06-13 20:20:40 +000077#include <string.h>
78
79typedef struct
80{
81 Tcl_Interp *interp;
Bram Moolenaar84a4c332012-02-22 16:01:56 +010082 int exitvalue;
Bram Moolenaar071d4272004-06-13 20:20:40 +000083 int range_start, range_end;
84 int lbase;
85 char *curbuf, *curwin;
86} tcl_info;
87
Bram Moolenaar84a4c332012-02-22 16:01:56 +010088static tcl_info tclinfo = { NULL, 0, 0, 0, 0, NULL, NULL };
Bram Moolenaar071d4272004-06-13 20:20:40 +000089
90#define VAR_RANGE1 "::vim::range(start)"
91#define VAR_RANGE2 "::vim::range(begin)"
92#define VAR_RANGE3 "::vim::range(end)"
93#define VAR_CURBUF "::vim::current(buffer)"
94#define VAR_CURWIN "::vim::current(window)"
95#define VAR_LBASE "::vim::lbase"
96#define VAR_CURLINE "line"
97#define VAR_CURLNUM "lnum"
98#define VARNAME_SIZE 64
99
100#define row2tcl(x) ((x) - (tclinfo.lbase==0))
101#define row2vim(x) ((x) + (tclinfo.lbase==0))
102#define col2tcl(x) ((x) + (tclinfo.lbase!=0))
103#define col2vim(x) ((x) - (tclinfo.lbase!=0))
104
105
106#define VIMOUT ((ClientData)1)
107#define VIMERR ((ClientData)2)
108
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100109// This appears to be new in Tcl 8.4.
Bram Moolenaar7df2d662005-01-25 22:18:08 +0000110#ifndef CONST84
111# define CONST84
112#endif
113
Bram Moolenaar071d4272004-06-13 20:20:40 +0000114/*
115 * List of Tcl interpreters who reference a vim window or buffer.
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100116 * Each buffer and window has its own list in the w_tcl_ref or b_tcl_ref
Bram Moolenaare344bea2005-09-01 20:46:49 +0000117 * struct member. We need this because Tcl can create sub-interpreters with
118 * the "interp" command, and each interpreter can reference all windows and
119 * buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000120 */
121struct ref
122{
123 struct ref *next;
124
125 Tcl_Interp *interp;
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100126 Tcl_Command cmd; // Tcl command that represents this object
127 Tcl_Obj *delcmd; // Tcl command to call when object is being del.
128 void *vimobj; // Vim window or buffer (win_T* or buf_T*)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000129};
130static char * tclgetbuffer _ANSI_ARGS_((Tcl_Interp *interp, buf_T *buf));
131static char * tclgetwindow _ANSI_ARGS_((Tcl_Interp *interp, win_T *win));
132static int tclsetdelcmd _ANSI_ARGS_((Tcl_Interp *interp, struct ref *reflist, void *vimobj, Tcl_Obj *delcmd));
133static int tclgetlinenum _ANSI_ARGS_ ((Tcl_Interp *interp, Tcl_Obj *obj, int *valueP, buf_T *buf));
134static win_T *tclfindwin _ANSI_ARGS_ ((buf_T *buf));
135static int tcldoexcommand _ANSI_ARGS_ ((Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int objn));
136static int tclsetoption _ANSI_ARGS_ ((Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int objn));
137static int tclvimexpr _ANSI_ARGS_ ((Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int objn));
138static void tcldelthisinterp _ANSI_ARGS_ ((void));
139
140static int vimerror _ANSI_ARGS_((Tcl_Interp *interp));
141static void tclmsg _ANSI_ARGS_((char *text));
142static void tclerrmsg _ANSI_ARGS_((char *text));
143static void tclupdatevars _ANSI_ARGS_((void));
144
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100145static struct ref refsdeleted; // dummy object for deleted ref list
Bram Moolenaar071d4272004-06-13 20:20:40 +0000146
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100147//////////////////////////////////////////////////////////////////////////////
148// TCL interface manager
149////////////////////////////////////////////////////////////////////////////
Bram Moolenaar071d4272004-06-13 20:20:40 +0000150
151#if defined(DYNAMIC_TCL) || defined(PROTO)
152# ifndef DYNAMIC_TCL_DLL
153# define DYNAMIC_TCL_DLL "tcl83.dll"
154# endif
155# ifndef DYNAMIC_TCL_VER
156# define DYNAMIC_TCL_VER "8.3"
157# endif
158
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100159# ifndef DYNAMIC_TCL // Just generating prototypes
Bram Moolenaar071d4272004-06-13 20:20:40 +0000160typedef int HANDLE;
161# endif
162
Ken Takatac97b3fe2023-10-11 21:27:06 +0200163# ifdef MSWIN
164# define TCL_PROC FARPROC
165# define load_dll vimLoadLib
166# define symbol_from_dll GetProcAddress
167# define close_dll FreeLibrary
168# define load_dll_error GetWin32Error
169# else
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100170# include <dlfcn.h>
171# define HANDLE void*
172# define TCL_PROC void*
173# define load_dll(n) dlopen((n), RTLD_LAZY|RTLD_GLOBAL)
174# define symbol_from_dll dlsym
175# define close_dll dlclose
Martin Tournoij1a3e5742021-07-24 13:57:29 +0200176# define load_dll_error dlerror
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100177# endif
178
Bram Moolenaar071d4272004-06-13 20:20:40 +0000179/*
Bram Moolenaara41b1392009-05-23 12:28:15 +0000180 * Declare HANDLE for tcl.dll and function pointers.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000181 */
182static HANDLE hTclLib = NULL;
Yee Cheng Chinf7f746b2023-09-30 12:28:50 +0200183Tcl_Interp* (*dll_Tcl_CreateInterp)(void);
Bram Moolenaar0b4db6b2013-10-02 14:25:44 +0200184void (*dll_Tcl_FindExecutable)(const void *);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000185
186/*
187 * Table of name to function pointer of tcl.
188 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000189static struct {
190 char* name;
191 TCL_PROC* ptr;
192} tcl_funcname_table[] = {
193 {"Tcl_CreateInterp", (TCL_PROC*)&dll_Tcl_CreateInterp},
Bram Moolenaar0b4db6b2013-10-02 14:25:44 +0200194 {"Tcl_FindExecutable", (TCL_PROC*)&dll_Tcl_FindExecutable},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000195 {NULL, NULL},
196};
197
198/*
199 * Make all runtime-links of tcl.
200 *
201 * 1. Get module handle using LoadLibraryEx.
Bram Moolenaara41b1392009-05-23 12:28:15 +0000202 * 2. Get pointer to tcl function by GetProcAddress.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000203 * 3. Repeat 2, until get all functions will be used.
204 *
205 * Parameter 'libname' provides name of DLL.
206 * Return OK or FAIL.
207 */
208 static int
209tcl_runtime_link_init(char *libname, int verbose)
210{
211 int i;
212
213 if (hTclLib)
214 return OK;
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100215 if (!(hTclLib = load_dll(libname)))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000216 {
217 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +0000218 semsg(_(e_could_not_load_library_str_str), libname, load_dll_error());
Bram Moolenaar071d4272004-06-13 20:20:40 +0000219 return FAIL;
220 }
221 for (i = 0; tcl_funcname_table[i].ptr; ++i)
222 {
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100223 if (!(*tcl_funcname_table[i].ptr = symbol_from_dll(hTclLib,
Bram Moolenaarabd56da2022-06-23 20:46:27 +0100224 tcl_funcname_table[i].name)))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000225 {
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100226 close_dll(hTclLib);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000227 hTclLib = NULL;
228 if (verbose)
Bram Moolenaarabd56da2022-06-23 20:46:27 +0100229 semsg(_(e_could_not_load_library_function_str),
230 tcl_funcname_table[i].name);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000231 return FAIL;
232 }
233 }
234 return OK;
235}
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100236#endif // defined(DYNAMIC_TCL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000237
238#ifdef DYNAMIC_TCL
239static char *find_executable_arg = NULL;
240#endif
241
242 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100243vim_tcl_init(char *arg)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000244{
Ken Takatac97b3fe2023-10-11 21:27:06 +0200245#ifdef DYNAMIC_TCL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000246 find_executable_arg = arg;
Ken Takatac97b3fe2023-10-11 21:27:06 +0200247#else
248 Tcl_FindExecutable(arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000249#endif
250}
251
252#if defined(DYNAMIC_TCL) || defined(PROTO)
253
254static int stubs_initialized = FALSE;
255
256/*
257 * Return TRUE if the TCL interface can be used.
258 */
259 int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100260tcl_enabled(int verbose)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000261{
262 if (!stubs_initialized && find_executable_arg != NULL
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100263 && tcl_runtime_link_init((char *)p_tcldll, verbose) == OK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000264 {
265 Tcl_Interp *interp;
266
Bram Moolenaarabd56da2022-06-23 20:46:27 +0100267 // Note: the library will allocate memory to store the executable name,
268 // which will be reported as possibly leaked by valgrind.
Bram Moolenaar0b4db6b2013-10-02 14:25:44 +0200269 dll_Tcl_FindExecutable(find_executable_arg);
270
Bram Moolenaar8a5115c2016-01-09 19:41:11 +0100271 if ((interp = dll_Tcl_CreateInterp()) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000272 {
Bram Moolenaarabd56da2022-06-23 20:46:27 +0100273 if (Tcl_InitStubs(interp, DYNAMIC_TCL_VER, 0) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000274 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000275 Tcl_DeleteInterp(interp);
276 stubs_initialized = TRUE;
277 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100278 // FIXME: When Tcl_InitStubs() was failed, how delete interp?
Bram Moolenaar071d4272004-06-13 20:20:40 +0000279 }
280 }
281 return stubs_initialized;
282}
283#endif
284
Bram Moolenaarafa76e12022-01-17 21:34:38 +0000285#if defined(EXITFREE) || defined(PROTO)
Bram Moolenaarabd56da2022-06-23 20:46:27 +0100286/*
287 * Called once when exiting.
288 */
Bram Moolenaarafa76e12022-01-17 21:34:38 +0000289 void
290vim_tcl_finalize(void)
291{
292# ifdef DYNAMIC_TCL
293 if (stubs_initialized)
294# endif
295 Tcl_Finalize();
296}
297#endif
298
Bram Moolenaar071d4272004-06-13 20:20:40 +0000299 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100300tcl_end(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000301{
Bram Moolenaar071d4272004-06-13 20:20:40 +0000302}
303
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100304/////////////////////////////////////////////////////////////////////////////
305// Tcl commands
306////////////////////////////////////////////////////////////////////////////
Bram Moolenaar071d4272004-06-13 20:20:40 +0000307
308/*
Bram Moolenaar84a4c332012-02-22 16:01:56 +0100309 * Replace standard "exit" command.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000310 *
Bram Moolenaar84a4c332012-02-22 16:01:56 +0100311 * Delete the Tcl interpreter; a new one will be created with the next
312 * :tcl command). The exit code is saved (and retrieved in tclexit()).
313 * Since Tcl's exit is never expected to return and this replacement
314 * does, then (except for a trivial case) additional Tcl commands will
315 * be run. Since the interpreter is now marked as deleted, an error
316 * will be returned -- typically "attempt to call eval in deleted
317 * interpreter". Hopefully, at this point, checks for TCL_ERROR take
318 * place and control percolates back up to Vim -- but with this new error
319 * string in the interpreter's result value. Therefore it would be
320 * useless for this routine to return the exit code via Tcl_SetResult().
Bram Moolenaar071d4272004-06-13 20:20:40 +0000321 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000322 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100323exitcmd(
324 ClientData dummy UNUSED,
325 Tcl_Interp *interp,
326 int objc,
327 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000328{
329 int value = 0;
330
331 switch (objc)
332 {
333 case 2:
334 if (Tcl_GetIntFromObj(interp, objv[1], &value) != TCL_OK)
335 break;
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100336 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +0000337 case 1:
Bram Moolenaar84a4c332012-02-22 16:01:56 +0100338 tclinfo.exitvalue = value;
339
340 Tcl_DeleteInterp(interp);
341 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000342 default:
343 Tcl_WrongNumArgs(interp, 1, objv, "?returnCode?");
344 }
345 return TCL_ERROR;
346}
347
Bram Moolenaar071d4272004-06-13 20:20:40 +0000348/*
349 * "::vim::beep" - what Vi[m] does best :-)
350 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000351 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100352beepcmd(
353 ClientData dummy UNUSED,
354 Tcl_Interp *interp,
355 int objc,
356 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000357{
358 if (objc != 1)
359 {
360 Tcl_WrongNumArgs(interp, 1, objv, NULL);
361 return TCL_ERROR;
362 }
Bram Moolenaar165bc692015-07-21 17:53:25 +0200363 vim_beep(BO_LANG);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000364 return TCL_OK;
365}
366
367/*
368 * "::vim::buffer list" - create a list of buffer commands.
Bram Moolenaarb8017e72007-05-10 18:59:07 +0000369 * "::vim::buffer {N}" - create buffer command for buffer N.
Bram Moolenaar84a4c332012-02-22 16:01:56 +0100370 * "::vim::buffer exists {N}" - test if buffer N exists.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000371 * "::vim::buffer new" - create a new buffer (not implemented)
372 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000373 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100374buffercmd(
375 ClientData dummy UNUSED,
376 Tcl_Interp *interp,
377 int objc,
378 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000379{
380 char *name;
381 buf_T *buf;
382 Tcl_Obj *resobj;
383 int err, n, idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000384 enum {BCMD_EXISTS, BCMD_LIST};
Bram Moolenaar7df2d662005-01-25 22:18:08 +0000385 static CONST84 char *bcmdoptions[] =
Bram Moolenaar071d4272004-06-13 20:20:40 +0000386 {
387 "exists", "list", (char *)0
388 };
389
390 if (objc < 2)
391 {
392 Tcl_WrongNumArgs(interp, 1, objv, "option");
393 return TCL_ERROR;
394 }
395 err = Tcl_GetIntFromObj(interp, objv[1], &n);
396 if (err == TCL_OK)
397 {
398 if (objc != 2)
399 {
400 Tcl_WrongNumArgs(interp, 1, objv, "bufNumber");
401 return TCL_ERROR;
402 }
Bram Moolenaar29323592016-07-24 22:04:11 +0200403 FOR_ALL_BUFFERS(buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000404 {
405 if (buf->b_fnum == n)
406 {
407 name = tclgetbuffer(interp, buf);
408 if (name == NULL)
409 return TCL_ERROR;
410 Tcl_SetResult(interp, name, TCL_VOLATILE);
411 return TCL_OK;
412 }
413 }
414 Tcl_SetResult(interp, _("invalid buffer number"), TCL_STATIC);
415 return TCL_ERROR;
416 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100417 Tcl_ResetResult(interp); // clear error from Tcl_GetIntFromObj
Bram Moolenaar071d4272004-06-13 20:20:40 +0000418
419 err = Tcl_GetIndexFromObj(interp, objv[1], bcmdoptions, "option", 0, &idx);
420 if (err != TCL_OK)
421 return err;
422 switch (idx)
423 {
424 case BCMD_LIST:
425 if (objc != 2)
426 {
427 Tcl_WrongNumArgs(interp, 2, objv, "");
428 err = TCL_ERROR;
429 break;
430 }
Bram Moolenaar29323592016-07-24 22:04:11 +0200431 FOR_ALL_BUFFERS(buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000432 {
433 name = tclgetbuffer(interp, buf);
434 if (name == NULL)
435 {
436 err = TCL_ERROR;
437 break;
438 }
439 Tcl_AppendElement(interp, name);
440 }
441 break;
442
443 case BCMD_EXISTS:
444 if (objc != 3)
445 {
446 Tcl_WrongNumArgs(interp, 2, objv, "bufNumber");
447 err = TCL_ERROR;
448 break;
449 }
450 err = Tcl_GetIntFromObj(interp, objv[2], &n);
451 if (err == TCL_OK)
452 {
453 buf = buflist_findnr(n);
454 resobj = Tcl_NewIntObj(buf != NULL);
455 Tcl_SetObjResult(interp, resobj);
456 }
457 break;
458
459 default:
460 Tcl_SetResult(interp, _("not implemented yet"), TCL_STATIC);
461 err = TCL_ERROR;
462 }
463 return err;
464}
465
466/*
467 * "::vim::window list" - create list of window commands.
468 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000469 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100470windowcmd(
471 ClientData dummy UNUSED,
472 Tcl_Interp *interp,
473 int objc,
474 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000475{
476 char *what, *string;
477 win_T *win;
478
479 if (objc != 2)
480 {
481 Tcl_WrongNumArgs(interp, 1, objv, "option");
482 return TCL_ERROR;
483 }
484 what = Tcl_GetStringFromObj(objv[1], NULL);
485 if (strcmp(what, "list") == 0)
486 {
487 FOR_ALL_WINDOWS(win)
488 {
489 string = tclgetwindow(interp, win);
490 if (string == NULL)
491 return TCL_ERROR;
492 Tcl_AppendElement(interp, string);
493 }
494 return TCL_OK;
495 }
496 Tcl_SetResult(interp, _("unknown option"), TCL_STATIC);
497 return TCL_ERROR;
498}
499
500/*
501 * flags for bufselfcmd and winselfcmd to indicate outstanding actions.
502 */
503#define FL_UPDATE_SCREEN (1<<0)
504#define FL_UPDATE_CURBUF (1<<1)
505#define FL_ADJUST_CURSOR (1<<2)
506
507/*
508 * This function implements the buffer commands.
509 */
510 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100511bufselfcmd(
512 ClientData ref,
513 Tcl_Interp *interp,
514 int objc,
515 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000516{
517 int opt, err, idx, flags;
518 int val1, val2, n, i;
519 buf_T *buf, *savebuf;
520 win_T *win, *savewin;
521 Tcl_Obj *resobj;
522 pos_T *pos;
523 char *line;
524
525 enum
526 {
527 BUF_APPEND, BUF_COMMAND, BUF_COUNT, BUF_DELCMD, BUF_DELETE, BUF_EXPR,
528 BUF_GET, BUF_INSERT, BUF_LAST, BUF_MARK, BUF_NAME, BUF_NUMBER,
529 BUF_OPTION, BUF_SET, BUF_WINDOWS
530 };
Bram Moolenaar7df2d662005-01-25 22:18:08 +0000531 static CONST84 char *bufoptions[] =
Bram Moolenaar071d4272004-06-13 20:20:40 +0000532 {
533 "append", "command", "count", "delcmd", "delete", "expr",
534 "get", "insert", "last", "mark", "name", "number",
535 "option", "set", "windows", (char *)0
536 };
537
538 if (objc < 2)
539 {
540 Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
541 return TCL_ERROR;
542 }
543
544 err = Tcl_GetIndexFromObj(interp, objv[1], bufoptions, "option", 0, &idx);
545 if (err != TCL_OK)
546 return err;
547
548 buf = (buf_T *)((struct ref *)ref)->vimobj;
549 savebuf = curbuf; curbuf = buf;
550 savewin = curwin; curwin = tclfindwin(buf);
551 flags = 0;
552 opt = 0;
553
554 switch (idx)
555 {
556 case BUF_COMMAND:
557 err = tcldoexcommand(interp, objc, objv, 2);
558 flags |= FL_UPDATE_SCREEN;
559 break;
560
561 case BUF_OPTION:
562 err = tclsetoption(interp, objc, objv, 2);
563 flags |= FL_UPDATE_SCREEN;
564 break;
565
566 case BUF_EXPR:
567 err = tclvimexpr(interp, objc, objv, 2);
568 break;
569
570 case BUF_NAME:
571 /*
572 * Get filename of buffer.
573 */
574 if (objc != 2)
575 {
576 Tcl_WrongNumArgs(interp, 2, objv, NULL);
577 err = TCL_ERROR;
578 break;
579 }
580 if (buf->b_ffname)
581 Tcl_SetResult(interp, (char *)buf->b_ffname, TCL_VOLATILE);
582 else
583 Tcl_SetResult(interp, "", TCL_STATIC);
584 break;
585
586 case BUF_LAST:
587 /*
588 * Get line number of last line.
589 */
590 opt = 1;
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100591 // fallthrough
Bram Moolenaar071d4272004-06-13 20:20:40 +0000592 case BUF_COUNT:
593 /*
594 * Get number of lines in buffer.
595 */
596 if (objc != 2)
597 {
598 Tcl_WrongNumArgs(interp, 2, objv, NULL);
599 err = TCL_ERROR;
600 break;
601 }
602 val1 = (int)buf->b_ml.ml_line_count;
603 if (opt)
604 val1 = row2tcl(val1);
605
606 resobj = Tcl_NewIntObj(val1);
607 Tcl_SetObjResult(interp, resobj);
608 break;
609
610 case BUF_NUMBER:
611 /*
612 * Get buffer's number.
613 */
614 if (objc != 2)
615 {
616 Tcl_WrongNumArgs(interp, 2, objv, NULL);
617 err = TCL_ERROR;
618 break;
619 }
620 resobj = Tcl_NewIntObj((int)buf->b_fnum);
621 Tcl_SetObjResult(interp, resobj);
622 break;
623
624 case BUF_GET:
625 if (objc != 3 && objc != 4)
626 {
627 Tcl_WrongNumArgs(interp, 2, objv, "lineNumber ?lineNumber?");
628 err = TCL_ERROR;
629 break;
630 }
631 err = tclgetlinenum(interp, objv[2], &val1, buf);
632 if (err != TCL_OK)
633 break;
634 if (objc == 4)
635 {
636 err = tclgetlinenum(interp, objv[3], &val2, buf);
637 if (err != TCL_OK)
638 break;
639 if (val1 > val2)
640 {
641 n = val1; val1 = val2; val2 = n;
642 }
643 Tcl_ResetResult(interp);
644
645 for (n = val1; n <= val2 && err == TCL_OK; n++)
646 {
647 line = (char *)ml_get_buf(buf, (linenr_T)n, FALSE);
648 if (line)
649 Tcl_AppendElement(interp, line);
650 else
651 err = TCL_ERROR;
652 }
653 }
Bram Moolenaar113d9de2022-08-08 15:49:18 +0100654 else
655 { // objc == 3
Bram Moolenaar071d4272004-06-13 20:20:40 +0000656 line = (char *)ml_get_buf(buf, (linenr_T)val1, FALSE);
657 Tcl_SetResult(interp, line, TCL_VOLATILE);
658 }
659 break;
660
661 case BUF_SET:
662 if (objc != 4 && objc != 5)
663 {
664 Tcl_WrongNumArgs(interp, 3, objv, "lineNumber ?lineNumber? stringOrList");
665 err = TCL_ERROR;
666 break;
667 }
668 err = tclgetlinenum(interp, objv[2], &val1, buf);
669 if (err != TCL_OK)
670 return TCL_ERROR;
671 if (objc == 4)
672 {
673 /*
674 * Replace one line with a string.
675 * $buf set {n} {string}
676 */
677 line = Tcl_GetStringFromObj(objv[3], NULL);
678 if (u_savesub((linenr_T)val1) != OK)
679 {
680 Tcl_SetResult(interp, _("cannot save undo information"), TCL_STATIC);
681 err = TCL_ERROR;
682 }
683 else
684 if (ml_replace((linenr_T)val1, (char_u *)line, TRUE) != OK)
685 {
686 Tcl_SetResult(interp, _("cannot replace line"), TCL_STATIC);
687 err = TCL_ERROR;
688 }
689 else
690 {
691 changed_bytes((linenr_T)val1, 0);
692 flags |= FL_UPDATE_CURBUF;
693 }
694 break;
695 }
696 else
697 {
698 /*
699 * Replace several lines with the elements of a Tcl list.
700 * $buf set {n} {m} {list}
701 * If the list contains more than {m}-{n}+1 elements, they
702 * are * inserted after line {m}. If the list contains fewer
703 * elements, * the lines from {n}+length({list}) through {m}
704 * are deleted.
705 */
706 int lc;
707 Tcl_Obj **lv;
708
709 err = tclgetlinenum(interp, objv[3], &val2, buf);
710 if (err != TCL_OK)
711 break;
712 err = Tcl_ListObjGetElements(interp, objv[4], &lc, &lv);
713 if (err != TCL_OK)
714 break;
715 if (val1 > val2)
716 {
717 n = val1;
718 val1 = val2;
719 val2 = n;
720 }
721
722 n = val1;
723 if (u_save((linenr_T)(val1 - 1), (linenr_T)(val2 + 1)) != OK)
724 {
725 Tcl_SetResult(interp, _("cannot save undo information"),
726 TCL_STATIC);
727 err = TCL_ERROR;
728 break;
729 }
730 flags |= FL_UPDATE_CURBUF;
731
732 for (i = 0; i < lc && n <= val2; i++)
733 {
734 line = Tcl_GetStringFromObj(lv[i], NULL);
735 if (ml_replace((linenr_T)n, (char_u *)line, TRUE) != OK)
736 goto setListError;
737 ++n;
738 }
739 if (i < lc)
740 {
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100741 // append lines
Bram Moolenaar071d4272004-06-13 20:20:40 +0000742 do
743 {
744 line = Tcl_GetStringFromObj(lv[i], NULL);
745 if (ml_append((linenr_T)(n - 1),
746 (char_u *)line, 0, FALSE) != OK)
747 goto setListError;
748 ++n;
749 ++i;
750 } while (i < lc);
751 }
752 else if (n <= val2)
753 {
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100754 // did not replace all lines, delete
Bram Moolenaar071d4272004-06-13 20:20:40 +0000755 i = n;
756 do
757 {
Bram Moolenaarca70c072020-05-30 20:30:46 +0200758 if (ml_delete((linenr_T)i) != OK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000759 goto setListError;
760 ++n;
761 } while (n <= val2);
762 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100763 lc -= val2 - val1 + 1; // number of lines to be replaced
Bram Moolenaar071d4272004-06-13 20:20:40 +0000764 mark_adjust((linenr_T)val1, (linenr_T)val2, (long)MAXLNUM,
765 (long)lc);
766 changed_lines((linenr_T)val1, 0, (linenr_T)val2 + 1, (long)lc);
767 break;
768 setListError:
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100769 u_undo(1); // ???
Bram Moolenaar071d4272004-06-13 20:20:40 +0000770 Tcl_SetResult(interp, _("cannot set line(s)"), TCL_STATIC);
771 err = TCL_ERROR;
772 }
773 break;
774
775 case BUF_DELETE:
776 if (objc != 3 && objc != 4)
777 {
778 Tcl_WrongNumArgs(interp, 3, objv, "lineNumber ?lineNumber?");
779 err = TCL_ERROR;
780 break;
781 }
782 err = tclgetlinenum(interp, objv[2], &val1, buf);
783 if (err != TCL_OK)
784 break;
785 val2 = val1;
786 if (objc == 4)
787 {
788 err = tclgetlinenum(interp, objv[3], &val2, buf);
789 if (err != TCL_OK)
790 return err;
791 if (val1 > val2)
792 {
793 i = val1; val1 = val2; val2 = i;
794 }
795 }
796 n = val2 - val1 + 1;
797 if (u_savedel((linenr_T)val1, (long)n) != OK)
798 {
799 Tcl_SetResult(interp, _("cannot save undo information"),
800 TCL_STATIC);
801 err = TCL_ERROR;
802 break;
803 }
804 for (i = 0; i < n; i++)
805 {
Bram Moolenaarca70c072020-05-30 20:30:46 +0200806 ml_delete((linenr_T)val1);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000807 err = vimerror(interp);
808 if (err != TCL_OK)
809 break;
810 }
811 if (i > 0)
812 deleted_lines_mark((linenr_T)val1, (long)i);
813 flags |= FL_ADJUST_CURSOR|FL_UPDATE_SCREEN;
814 break;
815
816 case BUF_MARK:
817 if (objc != 3)
818 {
819 Tcl_WrongNumArgs(interp, 2, objv, "markName");
820 err = TCL_ERROR;
821 break;
822 }
823 line = Tcl_GetStringFromObj(objv[2], NULL);
824
825 pos = NULL;
826 if (line[0] != '\0' && line[1] == '\0')
Bram Moolenaar071d4272004-06-13 20:20:40 +0000827 pos = getmark(line[0], FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000828 if (pos == NULL)
829 {
830 Tcl_SetResult(interp, _("invalid mark name"), TCL_STATIC);
831 err = TCL_ERROR;
832 break;
833 }
834 err = vimerror(interp);
835 if (err != TCL_OK)
836 break;
837 if (pos->lnum <= 0)
838 {
839 Tcl_SetResult(interp, _("mark not set"), TCL_STATIC);
840 err = TCL_ERROR;
841 }
842 else
843 {
844 char rbuf[64];
Bram Moolenaar555b2802005-05-19 21:08:39 +0000845
846 sprintf(rbuf, _("row %d column %d"),
847 (int)row2tcl(pos->lnum), (int)col2tcl(pos->col));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000848 Tcl_SetResult(interp, rbuf, TCL_VOLATILE);
849 }
850 break;
851
852 case BUF_INSERT:
853 opt = 1;
Bram Moolenaar2ab2e862019-12-04 21:24:53 +0100854 // fallthrough
Bram Moolenaar071d4272004-06-13 20:20:40 +0000855 case BUF_APPEND:
856 if (objc != 4)
857 {
858 Tcl_WrongNumArgs(interp, 2, objv, "lineNum text");
859 err = TCL_ERROR;
860 break;
861 }
862 err = tclgetlinenum(interp, objv[2], &val1, buf);
863 if (err != TCL_OK)
864 break;
865 if (opt)
866 --val1;
867 if (u_save((linenr_T)val1, (linenr_T)(val1+1)) != OK)
868 {
Bram Moolenaar555b2802005-05-19 21:08:39 +0000869 Tcl_SetResult(interp, _("cannot save undo information"),
870 TCL_STATIC);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000871 err = TCL_ERROR;
872 break;
873 }
874
875 line = Tcl_GetStringFromObj(objv[3], NULL);
876 if (ml_append((linenr_T)val1, (char_u *)line, 0, FALSE) != OK)
877 {
Bram Moolenaar555b2802005-05-19 21:08:39 +0000878 Tcl_SetResult(interp, _("cannot insert/append line"),
879 TCL_STATIC);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000880 err = TCL_ERROR;
881 break;
882 }
883 appended_lines_mark((linenr_T)val1, 1L);
884 flags |= FL_UPDATE_SCREEN;
885 break;
886
887 case BUF_WINDOWS:
888 /*
889 * Return list of window commands.
890 */
891 if (objc != 2)
892 {
893 Tcl_WrongNumArgs(interp, 2, objv, NULL);
894 err = TCL_ERROR;
895 break;
896 }
897 Tcl_ResetResult(interp);
898 FOR_ALL_WINDOWS(win)
899 {
900 if (win->w_buffer == buf)
901 {
902 line = tclgetwindow(interp, win);
903 if (line != NULL)
904 Tcl_AppendElement(interp, line);
905 else
906 {
907 err = TCL_ERROR;
908 break;
909 }
910 }
911 }
912 break;
913
914 case BUF_DELCMD:
915 /*
916 * Register deletion callback.
917 * TODO: Should be able to register multiple callbacks
918 */
919 if (objc != 3)
920 {
921 Tcl_WrongNumArgs(interp, 2, objv, "command");
922 err = TCL_ERROR;
923 break;
924 }
Bram Moolenaare344bea2005-09-01 20:46:49 +0000925 err = tclsetdelcmd(interp, buf->b_tcl_ref, (void *)buf, objv[2]);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000926 break;
927
928 default:
929 Tcl_SetResult(interp, _("not implemented yet"), TCL_STATIC);
930 err = TCL_ERROR;
931 }
932
933 if (flags & FL_UPDATE_CURBUF)
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100934 redraw_curbuf_later(UPD_NOT_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000935 curbuf = savebuf;
936 curwin = savewin;
937 if (flags & FL_ADJUST_CURSOR)
938 check_cursor();
939 if (flags & (FL_UPDATE_SCREEN | FL_UPDATE_CURBUF))
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100940 update_screen(UPD_NOT_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000941
942 return err;
943}
944
945/*
946 * This function implements the window commands.
947 */
948 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +0100949winselfcmd(
950 ClientData ref,
951 Tcl_Interp *interp,
952 int objc,
953 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000954{
955 int err, idx, flags;
956 int val1, val2;
957 Tcl_Obj *resobj;
958 win_T *savewin, *win;
959 buf_T *savebuf;
960 char *str;
961
962 enum
963 {
964 WIN_BUFFER, WIN_COMMAND, WIN_CURSOR, WIN_DELCMD, WIN_EXPR,
965 WIN_HEIGHT, WIN_OPTION
966 };
Bram Moolenaar7df2d662005-01-25 22:18:08 +0000967 static CONST84 char *winoptions[] =
Bram Moolenaar071d4272004-06-13 20:20:40 +0000968 {
969 "buffer", "command", "cursor", "delcmd", "expr",
970 "height", "option", (char *)0
971 };
972
973 if (objc < 2)
974 {
975 Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
976 return TCL_ERROR;
977 }
978
979 err = Tcl_GetIndexFromObj(interp, objv[1], winoptions, "option", 0, &idx);
980 if (err != TCL_OK)
981 return TCL_ERROR;
982
983 win = (win_T *)((struct ref *)ref)->vimobj;
984 savewin = curwin; curwin = win;
985 savebuf = curbuf; curbuf = win->w_buffer;
986 flags = 0;
987
988 switch (idx)
989 {
990 case WIN_OPTION:
991 err = tclsetoption(interp, objc, objv, 2);
992 flags |= FL_UPDATE_SCREEN;
993 break;
994
995 case WIN_COMMAND:
996 err = tcldoexcommand(interp, objc, objv, 2);
997 flags |= FL_UPDATE_SCREEN;
998 break;
999
1000 case WIN_EXPR:
1001 err = tclvimexpr(interp, objc, objv, 2);
1002 break;
1003
1004 case WIN_HEIGHT:
1005 if (objc == 3)
1006 {
1007 err = Tcl_GetIntFromObj(interp, objv[2], &val1);
1008 if (err != TCL_OK)
1009 break;
1010#ifdef FEAT_GUI
1011 need_mouse_correct = TRUE;
1012#endif
1013 win_setheight(val1);
1014 err = vimerror(interp);
1015 if (err != TCL_OK)
1016 break;
1017 }
1018 else
1019 if (objc != 2)
1020 {
1021 Tcl_WrongNumArgs(interp, 2, objv, "?value?");
1022 err = TCL_ERROR;
1023 break;
1024 }
1025
1026 resobj = Tcl_NewIntObj((int)(win->w_height));
1027 Tcl_SetObjResult(interp, resobj);
1028 break;
1029
1030 case WIN_BUFFER:
1031 if (objc != 2)
1032 {
1033 Tcl_WrongNumArgs(interp, 2, objv, NULL);
1034 err = TCL_ERROR;
1035 break;
1036 }
1037 str = tclgetbuffer(interp, win->w_buffer);
1038 if (str)
1039 Tcl_SetResult(interp, str, TCL_VOLATILE);
1040 else
1041 err = TCL_ERROR;
1042 break;
1043
1044 case WIN_DELCMD:
1045 if (objc != 3)
1046 {
1047 Tcl_WrongNumArgs(interp, 2, objv, "command");
1048 err = TCL_ERROR;
1049 break;
1050 }
Bram Moolenaare344bea2005-09-01 20:46:49 +00001051 err = tclsetdelcmd(interp, win->w_tcl_ref, (void *)win, objv[2]);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001052 break;
1053
1054 case WIN_CURSOR:
1055 if (objc > 4)
1056 {
1057 Tcl_WrongNumArgs(interp, 2, objv, "?arg1 ?arg2??");
1058 err = TCL_ERROR;
1059 break;
1060 }
1061 if (objc == 2)
1062 {
1063 char buf[64];
Bram Moolenaar555b2802005-05-19 21:08:39 +00001064
Bram Moolenaar071d4272004-06-13 20:20:40 +00001065 sprintf(buf, _("row %d column %d"), (int)row2tcl(win->w_cursor.lnum), (int)col2tcl(win->w_cursor.col));
1066 Tcl_SetResult(interp, buf, TCL_VOLATILE);
1067 break;
1068 }
1069 else if (objc == 3)
1070 {
1071 Tcl_Obj *part, *var;
1072
1073 part = Tcl_NewStringObj("row", -1);
1074 var = Tcl_ObjGetVar2(interp, objv[2], part, TCL_LEAVE_ERR_MSG);
1075 if (var == NULL)
1076 {
1077 err = TCL_ERROR;
1078 break;
1079 }
1080 err = tclgetlinenum(interp, var, &val1, win->w_buffer);
1081 if (err != TCL_OK)
1082 break;
1083 part = Tcl_NewStringObj("column", -1);
1084 var = Tcl_ObjGetVar2(interp, objv[2], part, TCL_LEAVE_ERR_MSG);
1085 if (var == NULL)
1086 {
1087 err = TCL_ERROR;
1088 break;
1089 }
1090 err = Tcl_GetIntFromObj(interp, var, &val2);
1091 if (err != TCL_OK)
1092 break;
1093 }
Bram Moolenaar113d9de2022-08-08 15:49:18 +01001094 else
1095 { // objc == 4
Bram Moolenaar071d4272004-06-13 20:20:40 +00001096 err = tclgetlinenum(interp, objv[2], &val1, win->w_buffer);
1097 if (err != TCL_OK)
1098 break;
1099 err = Tcl_GetIntFromObj(interp, objv[3], &val2);
1100 if (err != TCL_OK)
1101 break;
1102 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001103 // TODO: should check column
Bram Moolenaar071d4272004-06-13 20:20:40 +00001104 win->w_cursor.lnum = val1;
1105 win->w_cursor.col = col2vim(val2);
Bram Moolenaar53901442018-07-25 22:02:36 +02001106 win->w_set_curswant = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001107 flags |= FL_UPDATE_SCREEN;
1108 break;
1109
1110 default:
1111 Tcl_SetResult(interp, _("not implemented yet"), TCL_STATIC);
1112 break;
1113 }
1114
1115 curwin = savewin;
1116 curbuf = savebuf;
1117 if (flags & FL_UPDATE_SCREEN)
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001118 update_screen(UPD_NOT_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001119
1120 return err;
1121}
1122
1123
Bram Moolenaar071d4272004-06-13 20:20:40 +00001124 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001125commandcmd(
1126 ClientData dummy UNUSED,
1127 Tcl_Interp *interp,
1128 int objc,
1129 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001130{
1131 int err;
1132
1133 err = tcldoexcommand(interp, objc, objv, 1);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001134 update_screen(UPD_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001135 return err;
1136}
1137
Bram Moolenaar071d4272004-06-13 20:20:40 +00001138 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001139optioncmd(
1140 ClientData dummy UNUSED,
1141 Tcl_Interp *interp,
1142 int objc,
1143 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001144{
1145 int err;
1146
1147 err = tclsetoption(interp, objc, objv, 1);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001148 update_screen(UPD_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001149 return err;
1150}
1151
Bram Moolenaar071d4272004-06-13 20:20:40 +00001152 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001153exprcmd(
1154 ClientData dummy UNUSED,
1155 Tcl_Interp *interp,
1156 int objc,
1157 Tcl_Obj *CONST objv[])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001158{
1159 return tclvimexpr(interp, objc, objv, 1);
1160}
1161
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001162/////////////////////////////////////////////////////////////////////////////
1163// Support functions for Tcl commands
1164////////////////////////////////////////////////////////////////////////////
Bram Moolenaar071d4272004-06-13 20:20:40 +00001165
1166/*
1167 * Get a line number from 'obj' and convert it to vim's range.
1168 */
1169 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001170tclgetlinenum(
1171 Tcl_Interp *interp,
1172 Tcl_Obj *obj,
1173 int *valueP,
1174 buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001175{
1176 int err, i;
1177
1178 enum { LN_BEGIN, LN_BOTTOM, LN_END, LN_FIRST, LN_LAST, LN_START, LN_TOP };
1179
Bram Moolenaar7df2d662005-01-25 22:18:08 +00001180 static CONST84 char *keyw[] =
Bram Moolenaar071d4272004-06-13 20:20:40 +00001181 {
1182 "begin", "bottom", "end", "first", "last", "start", "top", (char *)0
1183 };
1184
1185 err = Tcl_GetIndexFromObj(interp, obj, keyw, "", 0, &i);
1186 if (err == TCL_OK)
1187 {
1188 switch (i)
1189 {
1190 case LN_BEGIN:
1191 case LN_FIRST:
1192 case LN_START:
1193 case LN_TOP:
1194 *valueP = 1;
1195 break;
1196 case LN_BOTTOM:
1197 case LN_END:
1198 case LN_LAST:
1199 *valueP = buf->b_ml.ml_line_count;
1200 break;
1201 }
1202 return TCL_OK;
1203 }
1204 Tcl_ResetResult(interp);
1205
1206 err = Tcl_GetIntFromObj(interp, obj, &i);
1207 if (err != TCL_OK)
1208 return err;
1209 i = row2vim(i);
1210 if (i < 1 || i > buf->b_ml.ml_line_count)
1211 {
1212 Tcl_SetResult(interp, _("line number out of range"), TCL_STATIC);
1213 return TCL_ERROR;
1214 }
1215 *valueP = i;
1216 return TCL_OK;
1217}
1218
1219/*
1220 * Find the first window in the window list that displays the buffer.
1221 */
1222 static win_T *
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001223tclfindwin(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001224{
1225 win_T *win;
1226
1227 FOR_ALL_WINDOWS(win)
1228 {
1229 if (win->w_buffer == buf)
1230 return win;
1231 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001232 return curwin; // keep current window context
Bram Moolenaar071d4272004-06-13 20:20:40 +00001233}
1234
1235/*
1236 * Do-it-all function for "::vim::command", "$buf command" and "$win command".
1237 */
1238 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001239tcldoexcommand(
1240 Tcl_Interp *interp,
1241 int objc,
1242 Tcl_Obj *CONST objv[],
1243 int objn)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001244{
1245 tcl_info saveinfo;
1246 int err, flag, nobjs;
1247 char *arg;
1248
1249 nobjs = objc - objn;
1250 if (nobjs < 1 || nobjs > 2)
1251 {
1252 Tcl_WrongNumArgs(interp, objn, objv, "?-quiet? exCommand");
1253 return TCL_ERROR;
1254 }
1255
1256 flag = 0;
1257 if (nobjs == 2)
1258 {
1259 arg = Tcl_GetStringFromObj(objv[objn], NULL);
1260 if (strcmp(arg, "-quiet") == 0)
1261 flag = 1;
1262 else
1263 {
1264 Tcl_ResetResult(interp);
1265 Tcl_AppendResult(interp, _("unknown flag: "), arg, (char *)0);
1266 return TCL_ERROR;
1267 }
1268 ++objn;
1269 }
1270
1271 memcpy(&saveinfo, &tclinfo, sizeof(tcl_info));
1272 tclinfo.interp = NULL;
1273 tclinfo.curwin = NULL;
1274 tclinfo.curbuf = NULL;
1275
1276 arg = Tcl_GetStringFromObj(objv[objn], NULL);
1277 if (flag)
1278 ++emsg_off;
1279 do_cmdline_cmd((char_u *)arg);
1280 if (flag)
1281 --emsg_off;
1282 err = vimerror(interp);
1283
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001284 // If the ex command created a new Tcl interpreter, remove it
Bram Moolenaar071d4272004-06-13 20:20:40 +00001285 if (tclinfo.interp)
1286 tcldelthisinterp();
1287 memcpy(&tclinfo, &saveinfo, sizeof(tcl_info));
1288 tclupdatevars();
1289
1290 return err;
1291}
1292
1293/*
1294 * Do-it-all function for "::vim::option", "$buf option" and "$win option".
1295 */
1296 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001297tclsetoption(
1298 Tcl_Interp *interp,
1299 int objc,
1300 Tcl_Obj *CONST objv[],
1301 int objn)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001302{
1303 int err, nobjs, idx;
1304 char_u *option;
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001305 getoption_T gov;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001306 long lval;
1307 char_u *sval;
1308 Tcl_Obj *resobj;
1309
1310 enum { OPT_OFF, OPT_ON, OPT_TOGGLE };
Bram Moolenaar7df2d662005-01-25 22:18:08 +00001311 static CONST84 char *optkw[] = { "off", "on", "toggle", (char *)0 };
Bram Moolenaar071d4272004-06-13 20:20:40 +00001312
1313 nobjs = objc - objn;
1314 if (nobjs != 1 && nobjs != 2)
1315 {
1316 Tcl_WrongNumArgs(interp, objn, objv, "vimOption ?value?");
1317 return TCL_ERROR;
1318 }
1319
1320 option = (char_u *)Tcl_GetStringFromObj(objv[objn], NULL);
1321 ++objn;
Yegappan Lakshmanan64095532021-12-06 11:03:55 +00001322 gov = get_option_value(option, &lval, &sval, NULL, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001323 err = TCL_OK;
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001324 switch (gov)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001325 {
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001326 case gov_string:
Bram Moolenaar071d4272004-06-13 20:20:40 +00001327 Tcl_SetResult(interp, (char *)sval, TCL_VOLATILE);
1328 vim_free(sval);
1329 break;
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001330 case gov_bool:
1331 case gov_number:
Bram Moolenaar071d4272004-06-13 20:20:40 +00001332 resobj = Tcl_NewLongObj(lval);
1333 Tcl_SetObjResult(interp, resobj);
1334 break;
1335 default:
1336 Tcl_SetResult(interp, _("unknown vimOption"), TCL_STATIC);
1337 return TCL_ERROR;
1338 }
1339 if (nobjs == 2)
1340 {
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001341 if (gov != gov_string)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001342 {
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001343 sval = NULL; // avoid compiler warning
Bram Moolenaar071d4272004-06-13 20:20:40 +00001344 err = Tcl_GetIndexFromObj(interp, objv[objn], optkw, "", 0, &idx);
1345 if (err != TCL_OK)
1346 {
1347 Tcl_ResetResult(interp);
1348 err = Tcl_GetLongFromObj(interp, objv[objn], &lval);
1349 }
1350 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001351 {
Bram Moolenaar1779ff42020-12-31 18:11:16 +01001352 switch (idx)
1353 {
1354 case OPT_ON:
1355 lval = 1;
1356 break;
1357 case OPT_OFF:
1358 lval = 0;
1359 break;
1360 case OPT_TOGGLE:
1361 lval = !lval;
1362 break;
1363 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001364 }
1365 }
1366 else
1367 sval = (char_u *)Tcl_GetStringFromObj(objv[objn], NULL);
1368 if (err == TCL_OK)
1369 {
Bram Moolenaar31e5c602022-04-15 13:53:33 +01001370 set_option_value_give_err(option, lval, sval, OPT_LOCAL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001371 err = vimerror(interp);
1372 }
1373 }
1374 return err;
1375}
1376
1377/*
1378 * Do-it-all function for "::vim::expr", "$buf expr" and "$win expr".
1379 */
1380 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001381tclvimexpr(
1382 Tcl_Interp *interp,
1383 int objc,
1384 Tcl_Obj *CONST objv[],
1385 int objn)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001386{
1387#ifdef FEAT_EVAL
1388 char *expr, *str;
1389#endif
1390 int err;
1391
1392 if (objc - objn != 1)
1393 {
1394 Tcl_WrongNumArgs(interp, objn, objv, "vimExpr");
1395 return TCL_ERROR;
1396 }
1397
1398#ifdef FEAT_EVAL
1399 expr = Tcl_GetStringFromObj(objv[objn], NULL);
Bram Moolenaara4e0b972022-10-01 19:43:52 +01001400 str = (char *)eval_to_string((char_u *)expr, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001401 if (str == NULL)
1402 Tcl_SetResult(interp, _("invalid expression"), TCL_STATIC);
1403 else
Bram Moolenaar92959fa2018-07-04 22:12:25 +02001404 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001405 Tcl_SetResult(interp, str, TCL_VOLATILE);
Bram Moolenaar92959fa2018-07-04 22:12:25 +02001406 vim_free(str);
1407 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001408 err = vimerror(interp);
1409#else
1410 Tcl_SetResult(interp, _("expressions disabled at compile time"), TCL_STATIC);
1411 err = TCL_ERROR;
1412#endif
1413
1414 return err;
1415}
1416
1417/*
1418 * Check for internal vim errors.
1419 */
1420 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001421vimerror(Tcl_Interp *interp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001422{
1423 if (got_int)
1424 {
1425 Tcl_SetResult(interp, _("keyboard interrupt"), TCL_STATIC);
1426 return TCL_ERROR;
1427 }
1428 else if (did_emsg)
1429 {
Bram Moolenaar86181df2020-05-11 23:14:04 +02001430 Tcl_SetResult(interp, _("Vim error"), TCL_STATIC);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001431 return TCL_ERROR;
1432 }
1433 return TCL_OK;
1434}
1435
1436/*
1437 * Functions that handle the reference lists:
1438 * delref() - callback for Tcl's DeleteCommand
1439 * tclgetref() - find/create Tcl command for a win_T* or buf_T* object
1440 * tclgetwindow() - window frontend for tclgetref()
1441 * tclgetbuffer() - buffer frontend for tclgetref()
1442 * tclsetdelcmd() - add Tcl callback command to a vim object
1443 */
1444 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001445delref(ClientData cref)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001446{
1447 struct ref *ref = (struct ref *)cref;
1448
1449 if (ref->delcmd)
1450 {
1451 Tcl_DecrRefCount(ref->delcmd);
1452 ref->delcmd = NULL;
1453 }
1454 ref->interp = NULL;
1455}
1456
1457 static char *
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001458tclgetref(
1459 Tcl_Interp *interp,
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001460 void **refstartP, // ptr to w_tcl_ref/b_tcl-ref member of
1461 // win_T/buf_T struct
1462 char *prefix, // "win" or "buf"
1463 void *vimobj, // win_T* or buf_T*
1464 Tcl_ObjCmdProc *proc) // winselfcmd or bufselfcmd
Bram Moolenaar071d4272004-06-13 20:20:40 +00001465{
1466 struct ref *ref, *unused = NULL;
1467 static char name[VARNAME_SIZE];
1468 Tcl_Command cmd;
1469
1470 ref = (struct ref *)(*refstartP);
1471 if (ref == &refsdeleted)
1472 {
1473 Tcl_SetResult(interp, _("cannot create buffer/window command: object is being deleted"), TCL_STATIC);
1474 return NULL;
1475 }
1476
1477 while (ref != NULL)
1478 {
1479 if (ref->interp == interp)
1480 break;
1481 if (ref->interp == NULL)
1482 unused = ref;
1483 ref = ref->next;
1484 }
1485
1486 if (ref)
Bram Moolenaar555b2802005-05-19 21:08:39 +00001487 vim_snprintf(name, sizeof(name), "::vim::%s",
1488 Tcl_GetCommandName(interp, ref->cmd));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001489 else
1490 {
1491 if (unused)
1492 ref = unused;
1493 else
1494 {
1495 ref = (struct ref *)Tcl_Alloc(sizeof(struct ref));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001496 ref->interp = NULL;
1497 ref->next = (struct ref *)(*refstartP);
1498 (*refstartP) = (void *)ref;
1499 }
1500
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001501 // This might break on some exotic systems...
Bram Moolenaar555b2802005-05-19 21:08:39 +00001502 vim_snprintf(name, sizeof(name), "::vim::%s_%lx",
1503 prefix, (unsigned long)vimobj);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001504 cmd = Tcl_CreateObjCommand(interp, name, proc,
1505 (ClientData)ref, (Tcl_CmdDeleteProc *)delref);
1506 if (!cmd)
1507 return NULL;
1508
1509 ref->interp = interp;
1510 ref->cmd = cmd;
1511 ref->delcmd = NULL;
1512 ref->vimobj = vimobj;
1513 }
1514 return name;
1515}
1516
1517 static char *
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001518tclgetwindow(Tcl_Interp *interp, win_T *win)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001519{
Bram Moolenaare344bea2005-09-01 20:46:49 +00001520 return tclgetref(interp, &(win->w_tcl_ref), "win", (void *)win, winselfcmd);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001521}
1522
1523 static char *
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001524tclgetbuffer(Tcl_Interp *interp, buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001525{
Bram Moolenaare344bea2005-09-01 20:46:49 +00001526 return tclgetref(interp, &(buf->b_tcl_ref), "buf", (void *)buf, bufselfcmd);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001527}
1528
1529 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001530tclsetdelcmd(
1531 Tcl_Interp *interp,
1532 struct ref *reflist,
1533 void *vimobj,
1534 Tcl_Obj *delcmd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001535{
1536 if (reflist == &refsdeleted)
1537 {
1538 Tcl_SetResult(interp, _("cannot register callback command: buffer/window is already being deleted"), TCL_STATIC);
1539 return TCL_ERROR;
1540 }
1541
1542 while (reflist != NULL)
1543 {
1544 if (reflist->interp == interp && reflist->vimobj == vimobj)
1545 {
1546 if (reflist->delcmd)
1547 Tcl_DecrRefCount(reflist->delcmd);
1548 Tcl_IncrRefCount(delcmd);
1549 reflist->delcmd = delcmd;
1550 return TCL_OK;
1551 }
1552 reflist = reflist->next;
1553 }
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001554 // This should never happen. Famous last word?
RestorerZ68ebcee2023-05-31 17:12:14 +01001555 iemsg(e_tcl_fatal_error_reflist_corrupt_please_report_this);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001556 Tcl_SetResult(interp, _("cannot register callback command: buffer/window reference not found"), TCL_STATIC);
1557 return TCL_ERROR;
1558}
1559
1560
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001561////////////////////////////////////////////
1562// I/O Channel
1563////////////////////////////////////////////
Bram Moolenaar071d4272004-06-13 20:20:40 +00001564
Bram Moolenaar071d4272004-06-13 20:20:40 +00001565 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001566tcl_channel_close(ClientData instance, Tcl_Interp *interp UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001567{
1568 int err = 0;
1569
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001570 // currently does nothing
Bram Moolenaar071d4272004-06-13 20:20:40 +00001571
1572 if (instance != VIMOUT && instance != VIMERR)
1573 {
1574 Tcl_SetErrno(EBADF);
1575 err = EBADF;
1576 }
1577 return err;
1578}
1579
Bram Moolenaar071d4272004-06-13 20:20:40 +00001580 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001581tcl_channel_input(
1582 ClientData instance UNUSED,
1583 char *buf UNUSED,
1584 int bufsiz UNUSED,
1585 int *errptr)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001586{
1587
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001588 // input is currently not supported
Bram Moolenaar071d4272004-06-13 20:20:40 +00001589
1590 Tcl_SetErrno(EINVAL);
1591 if (errptr)
1592 *errptr = EINVAL;
1593 return -1;
1594}
1595
1596 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001597tcl_channel_output(
1598 ClientData instance,
1599 const char *buf,
1600 int bufsiz,
1601 int *errptr)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001602{
1603 char_u *str;
1604 int result;
1605
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001606 // The buffer is not guaranteed to be 0-terminated, and we don't if
1607 // there is enough room to add a '\0'. So we have to create a copy
1608 // of the buffer...
Bram Moolenaar071d4272004-06-13 20:20:40 +00001609 str = vim_strnsave((char_u *)buf, bufsiz);
1610 if (!str)
1611 {
1612 Tcl_SetErrno(ENOMEM);
1613 if (errptr)
1614 *errptr = ENOMEM;
1615 return -1;
1616 }
1617
1618 result = bufsiz;
1619 if (instance == VIMOUT)
1620 tclmsg((char *)str);
1621 else
1622 if (instance == VIMERR)
1623 tclerrmsg((char *)str);
1624 else
1625 {
1626 Tcl_SetErrno(EBADF);
1627 if (errptr)
1628 *errptr = EBADF;
1629 result = -1;
1630 }
1631 vim_free(str);
1632 return result;
1633}
1634
Bram Moolenaar071d4272004-06-13 20:20:40 +00001635 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001636tcl_channel_watch(ClientData instance UNUSED, int mask UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001637{
1638 Tcl_SetErrno(EINVAL);
1639}
1640
Bram Moolenaar071d4272004-06-13 20:20:40 +00001641 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001642tcl_channel_gethandle(
1643 ClientData instance UNUSED,
1644 int direction UNUSED,
1645 ClientData *handleptr UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001646{
1647 Tcl_SetErrno(EINVAL);
1648 return EINVAL;
1649}
1650
1651
Bram Moolenaar0d6f8352016-01-27 11:07:47 +01001652static Tcl_ChannelType tcl_channel_type =
Bram Moolenaar071d4272004-06-13 20:20:40 +00001653{
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001654 "vimmessage", // typeName
1655 TCL_CHANNEL_VERSION_2, // version
1656 tcl_channel_close, // closeProc
1657 tcl_channel_input, // inputProc
1658 tcl_channel_output, // outputProc
1659 NULL, // seekProc
1660 NULL, // setOptionProc
1661 NULL, // getOptionProc
1662 tcl_channel_watch, // watchProc
1663 tcl_channel_gethandle, // getHandleProc
1664 NULL, // close2Proc
1665 NULL, // blockModeProc
Bram Moolenaara41b1392009-05-23 12:28:15 +00001666#ifdef TCL_CHANNEL_VERSION_2
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001667 NULL, // flushProc
1668 NULL, // handlerProc
Bram Moolenaara41b1392009-05-23 12:28:15 +00001669#endif
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001670// The following should not be necessary since TCL_CHANNEL_VERSION_2 was
1671// set above
Bram Moolenaara41b1392009-05-23 12:28:15 +00001672#ifdef TCL_CHANNEL_VERSION_3
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001673 NULL, // wideSeekProc
Bram Moolenaara41b1392009-05-23 12:28:15 +00001674#endif
1675#ifdef TCL_CHANNEL_VERSION_4
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001676 NULL, // threadActionProc
Bram Moolenaara41b1392009-05-23 12:28:15 +00001677#endif
1678#ifdef TCL_CHANNEL_VERSION_5
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001679 NULL // truncateProc
Bram Moolenaara41b1392009-05-23 12:28:15 +00001680#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001681};
1682
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001683///////////////////////////////////
1684// Interface to vim
1685//////////////////////////////////
Bram Moolenaar071d4272004-06-13 20:20:40 +00001686
1687 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001688tclupdatevars(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001689{
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001690 char varname[VARNAME_SIZE]; // must be writeable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001691 char *name;
1692
1693 strcpy(varname, VAR_RANGE1);
1694 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1695 strcpy(varname, VAR_RANGE2);
1696 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1697 strcpy(varname, VAR_RANGE3);
1698 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1699
1700 strcpy(varname, VAR_LBASE);
1701 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1702
1703 name = tclgetbuffer(tclinfo.interp, curbuf);
1704 strcpy(tclinfo.curbuf, name);
1705 strcpy(varname, VAR_CURBUF);
1706 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1707
1708 name = tclgetwindow(tclinfo.interp, curwin);
1709 strcpy(tclinfo.curwin, name);
1710 strcpy(varname, VAR_CURWIN);
1711 Tcl_UpdateLinkedVar(tclinfo.interp, varname);
1712}
1713
1714
1715 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001716tclinit(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001717{
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001718 char varname[VARNAME_SIZE]; // Tcl_LinkVar requires writeable varname
Bram Moolenaar071d4272004-06-13 20:20:40 +00001719 char *name;
1720
1721#ifdef DYNAMIC_TCL
1722 if (!tcl_enabled(TRUE))
1723 {
Bram Moolenaar1d423ef2022-01-02 21:26:16 +00001724 emsg(_(e_sorry_this_command_is_disabled_tcl_library_could_not_be_loaded));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001725 return FAIL;
1726 }
1727#endif
1728
1729 if (!tclinfo.interp)
1730 {
1731 Tcl_Interp *interp;
1732 static Tcl_Channel ch1, ch2;
1733
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001734 // Create replacement channels for stdout and stderr; this has to be
1735 // done each time an interpreter is created since the channels are closed
1736 // when the interpreter is deleted
Bram Moolenaar0d6f8352016-01-27 11:07:47 +01001737 ch1 = Tcl_CreateChannel(&tcl_channel_type, "vimout", VIMOUT, TCL_WRITABLE);
1738 ch2 = Tcl_CreateChannel(&tcl_channel_type, "vimerr", VIMERR, TCL_WRITABLE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001739 Tcl_SetStdChannel(ch1, TCL_STDOUT);
1740 Tcl_SetStdChannel(ch2, TCL_STDERR);
1741
1742 interp = Tcl_CreateInterp();
1743 Tcl_Preserve(interp);
1744 if (Tcl_Init(interp) == TCL_ERROR)
1745 {
1746 Tcl_Release(interp);
1747 Tcl_DeleteInterp(interp);
1748 return FAIL;
1749 }
1750#if 0
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001751 // VIM sure is interactive
Bram Moolenaar071d4272004-06-13 20:20:40 +00001752 Tcl_SetVar(interp, "tcl_interactive", "1", TCL_GLOBAL_ONLY);
1753#endif
1754
1755 Tcl_SetChannelOption(interp, ch1, "-buffering", "line");
Bram Moolenaar4f974752019-02-17 17:44:42 +01001756#ifdef MSWIN
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001757 Tcl_SetChannelOption(interp, ch1, "-translation", "lf");
1758#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001759 Tcl_SetChannelOption(interp, ch2, "-buffering", "line");
Bram Moolenaar4f974752019-02-17 17:44:42 +01001760#ifdef MSWIN
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001761 Tcl_SetChannelOption(interp, ch2, "-translation", "lf");
1762#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001763
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001764 // replace standard Tcl exit command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001765 Tcl_DeleteCommand(interp, "exit");
1766 Tcl_CreateObjCommand(interp, "exit", exitcmd,
1767 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001768
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001769 // new commands, in ::vim namespace
Bram Moolenaar071d4272004-06-13 20:20:40 +00001770 Tcl_CreateObjCommand(interp, "::vim::buffer", buffercmd,
1771 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1772 Tcl_CreateObjCommand(interp, "::vim::window", windowcmd,
1773 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1774 Tcl_CreateObjCommand(interp, "::vim::command", commandcmd,
1775 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1776 Tcl_CreateObjCommand(interp, "::vim::beep", beepcmd,
1777 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1778 Tcl_CreateObjCommand(interp, "::vim::option", optioncmd,
1779 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1780 Tcl_CreateObjCommand(interp, "::vim::expr", exprcmd,
1781 (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
1782
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001783 // "lbase" variable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001784 tclinfo.lbase = 1;
1785 strcpy(varname, VAR_LBASE);
1786 Tcl_LinkVar(interp, varname, (char *)&tclinfo.lbase, TCL_LINK_INT);
1787
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001788 // "range" variable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001789 tclinfo.range_start = eap->line1;
1790 strcpy(varname, VAR_RANGE1);
1791 Tcl_LinkVar(interp, varname, (char *)&tclinfo.range_start, TCL_LINK_INT|TCL_LINK_READ_ONLY);
1792 strcpy(varname, VAR_RANGE2);
1793 Tcl_LinkVar(interp, varname, (char *)&tclinfo.range_start, TCL_LINK_INT|TCL_LINK_READ_ONLY);
1794 tclinfo.range_end = eap->line2;
1795 strcpy(varname, VAR_RANGE3);
1796 Tcl_LinkVar(interp, varname, (char *)&tclinfo.range_end, TCL_LINK_INT|TCL_LINK_READ_ONLY);
1797
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001798 // "current" variable
Bram Moolenaar071d4272004-06-13 20:20:40 +00001799 tclinfo.curbuf = Tcl_Alloc(VARNAME_SIZE);
1800 tclinfo.curwin = Tcl_Alloc(VARNAME_SIZE);
1801 name = tclgetbuffer(interp, curbuf);
1802 strcpy(tclinfo.curbuf, name);
1803 strcpy(varname, VAR_CURBUF);
1804 Tcl_LinkVar(interp, varname, (char *)&tclinfo.curbuf, TCL_LINK_STRING|TCL_LINK_READ_ONLY);
1805 name = tclgetwindow(interp, curwin);
1806 strcpy(tclinfo.curwin, name);
1807 strcpy(varname, VAR_CURWIN);
1808 Tcl_LinkVar(interp, varname, (char *)&tclinfo.curwin, TCL_LINK_STRING|TCL_LINK_READ_ONLY);
1809
1810 tclinfo.interp = interp;
1811 }
1812 else
1813 {
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001814 // Interpreter already exists, just update variables
Bram Moolenaar071d4272004-06-13 20:20:40 +00001815 tclinfo.range_start = row2tcl(eap->line1);
1816 tclinfo.range_end = row2tcl(eap->line2);
1817 tclupdatevars();
1818 }
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001819
1820 tclinfo.exitvalue = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001821 return OK;
1822}
1823
1824 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001825tclerrmsg(char *text)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001826{
1827 char *next;
1828
1829 while ((next=strchr(text, '\n')))
1830 {
1831 *next++ = '\0';
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001832 emsg(text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001833 text = next;
1834 }
1835 if (*text)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001836 emsg(text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001837}
1838
1839 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001840tclmsg(char *text)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841{
1842 char *next;
1843
1844 while ((next=strchr(text, '\n')))
1845 {
1846 *next++ = '\0';
Bram Moolenaar32526b32019-01-19 17:43:09 +01001847 msg(text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001848 text = next;
1849 }
1850 if (*text)
Bram Moolenaar32526b32019-01-19 17:43:09 +01001851 msg(text);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001852}
1853
1854 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001855tcldelthisinterp(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001856{
1857 if (!Tcl_InterpDeleted(tclinfo.interp))
1858 Tcl_DeleteInterp(tclinfo.interp);
1859 Tcl_Release(tclinfo.interp);
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001860 // The interpreter is now gets deleted. All registered commands (esp.
1861 // window and buffer commands) are deleted, triggering their deletion
1862 // callback, which deletes all refs pointing to this interpreter.
1863 // We could garbage-collect the unused ref structs in all windows and
1864 // buffers, but unless the user creates hundreds of sub-interpreters
1865 // all referring to lots of windows and buffers, this is hardly worth
1866 // the effort. Unused refs are recycled by other interpreters, and
1867 // all refs are free'd when the window/buffer gets closed by vim.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001868
1869 tclinfo.interp = NULL;
1870 Tcl_Free(tclinfo.curbuf);
1871 Tcl_Free(tclinfo.curwin);
1872 tclinfo.curbuf = tclinfo.curwin = NULL;
1873}
1874
1875 static int
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001876tclexit(int error)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001877{
1878 int newerr = OK;
1879
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001880 if (Tcl_InterpDeleted(tclinfo.interp) // True if we intercepted Tcl's exit command
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001881#if (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 5) || TCL_MAJOR_VERSION > 8
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001882 || Tcl_LimitExceeded(tclinfo.interp) // True if the interpreter cannot continue
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001883#endif
1884 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00001885 {
Bram Moolenaar555b2802005-05-19 21:08:39 +00001886 char buf[50];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001887
Bram Moolenaar1d423ef2022-01-02 21:26:16 +00001888 sprintf(buf, _(e_exit_code_nr), tclinfo.exitvalue);
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001889 tclerrmsg(buf);
1890 if (tclinfo.exitvalue == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001891 {
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001892 did_emsg = 0;
1893 newerr = OK;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001894 }
1895 else
Bram Moolenaar84a4c332012-02-22 16:01:56 +01001896 newerr = FAIL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001897
1898 tcldelthisinterp();
1899 }
1900 else
1901 {
1902 char *result;
1903
Bram Moolenaar7df2d662005-01-25 22:18:08 +00001904 result = (char *)Tcl_GetStringResult(tclinfo.interp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001905 if (error == TCL_OK)
1906 {
1907 tclmsg(result);
1908 newerr = OK;
1909 }
1910 else
1911 {
1912 tclerrmsg(result);
1913 newerr = FAIL;
1914 }
1915 }
1916
1917 return newerr;
1918}
1919
1920/*
1921 * ":tcl"
1922 */
1923 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001924ex_tcl(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001925{
1926 char_u *script;
1927 int err;
1928
1929 script = script_get(eap, eap->arg);
1930 if (!eap->skip)
1931 {
1932 err = tclinit(eap);
1933 if (err == OK)
1934 {
1935 Tcl_AllowExceptions(tclinfo.interp);
1936 if (script == NULL)
1937 err = Tcl_Eval(tclinfo.interp, (char *)eap->arg);
1938 else
1939 err = Tcl_Eval(tclinfo.interp, (char *)script);
1940 err = tclexit(err);
1941 }
1942 }
1943 vim_free(script);
1944}
1945
1946/*
1947 * ":tclfile"
1948 */
1949 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001950ex_tclfile(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001951{
1952 char *file = (char *)eap->arg;
1953 int err;
1954
1955 err = tclinit(eap);
1956 if (err == OK)
1957 {
1958 Tcl_AllowExceptions(tclinfo.interp);
1959 err = Tcl_EvalFile(tclinfo.interp, file);
1960 err = tclexit(err);
1961 }
1962}
1963
1964/*
1965 * ":tcldo"
1966 */
1967 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01001968ex_tcldo(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001969{
1970 char *script, *line;
1971 int err, rs, re, lnum;
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01001972 char var_lnum[VARNAME_SIZE]; // must be writeable memory
Bram Moolenaar071d4272004-06-13 20:20:40 +00001973 char var_line[VARNAME_SIZE];
1974 linenr_T first_line = 0;
1975 linenr_T last_line = 0;
Bram Moolenaara4c906a2017-01-29 23:26:37 +01001976 buf_T *was_curbuf = curbuf;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001977
1978 rs = eap->line1;
1979 re = eap->line2;
1980 script = (char *)eap->arg;
1981 strcpy(var_lnum, VAR_CURLNUM);
1982 strcpy(var_line, VAR_CURLINE);
1983
1984 err = tclinit(eap);
1985 if (err != OK)
1986 return;
1987
1988 lnum = row2tcl(rs);
1989 Tcl_LinkVar(tclinfo.interp, var_lnum, (char *)&lnum, TCL_LINK_INT|TCL_LINK_READ_ONLY);
1990 err = TCL_OK;
1991 if (u_save((linenr_T)(rs-1), (linenr_T)(re+1)) != OK)
1992 {
1993 Tcl_SetResult(tclinfo.interp, _("cannot save undo information"), TCL_STATIC);
1994 err = TCL_ERROR;
1995 }
1996 while (err == TCL_OK && rs <= re)
1997 {
Bram Moolenaara4c906a2017-01-29 23:26:37 +01001998 if ((linenr_T)rs > curbuf->b_ml.ml_line_count)
1999 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002000 line = (char *)ml_get_buf(curbuf, (linenr_T)rs, FALSE);
2001 if (!line)
2002 {
2003 Tcl_SetResult(tclinfo.interp, _("cannot get line"), TCL_STATIC);
2004 err = TCL_ERROR;
2005 break;
2006 }
2007 Tcl_SetVar(tclinfo.interp, var_line, line, 0);
2008 Tcl_AllowExceptions(tclinfo.interp);
2009 err = Tcl_Eval(tclinfo.interp, script);
Bram Moolenaar84a4c332012-02-22 16:01:56 +01002010 if (err != TCL_OK
2011 || Tcl_InterpDeleted(tclinfo.interp)
2012#if (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 5) || TCL_MAJOR_VERSION > 8
2013 || Tcl_LimitExceeded(tclinfo.interp)
2014#endif
zeertzjqe99f0682024-01-29 19:32:39 +01002015 || curbuf != was_curbuf
2016 || (linenr_T)rs > curbuf->b_ml.ml_line_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002017 break;
Bram Moolenaar7df2d662005-01-25 22:18:08 +00002018 line = (char *)Tcl_GetVar(tclinfo.interp, var_line, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002019 if (line)
2020 {
2021 if (ml_replace((linenr_T)rs, (char_u *)line, TRUE) != OK)
2022 {
2023 Tcl_SetResult(tclinfo.interp, _("cannot replace line"), TCL_STATIC);
2024 err = TCL_ERROR;
2025 break;
2026 }
2027 if (first_line == 0)
2028 first_line = rs;
2029 last_line = rs;
2030 }
2031 ++rs;
2032 ++lnum;
2033 Tcl_UpdateLinkedVar(tclinfo.interp, var_lnum);
2034 }
2035 if (first_line)
2036 changed_lines(first_line, 0, last_line + 1, (long)0);
2037
2038 Tcl_UnsetVar(tclinfo.interp, var_line, 0);
2039 Tcl_UnlinkVar(tclinfo.interp, var_lnum);
2040 if (err == TCL_OK)
2041 Tcl_ResetResult(tclinfo.interp);
2042
2043 (void)tclexit(err);
2044}
2045
2046 static void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01002047tcldelallrefs(struct ref *ref)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002048{
2049 struct ref *next;
2050 int err;
2051 char *result;
2052
Bram Moolenaar858b96f2016-01-10 16:12:24 +01002053#ifdef DYNAMIC_TCL
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01002054 // TODO: this code currently crashes Vim on exit
Bram Moolenaar8a5115c2016-01-09 19:41:11 +01002055 if (exiting)
2056 return;
Bram Moolenaar858b96f2016-01-10 16:12:24 +01002057#endif
Bram Moolenaar8a5115c2016-01-09 19:41:11 +01002058
Bram Moolenaar071d4272004-06-13 20:20:40 +00002059 while (ref != NULL)
2060 {
2061 next = ref->next;
2062 if (ref->interp)
2063 {
2064 if (ref->delcmd)
2065 {
2066 err = Tcl_GlobalEvalObj(ref->interp, ref->delcmd);
2067 if (err != TCL_OK)
2068 {
Bram Moolenaar7df2d662005-01-25 22:18:08 +00002069 result = (char *)Tcl_GetStringResult(ref->interp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002070 if (result)
2071 tclerrmsg(result);
2072 }
2073 Tcl_DecrRefCount(ref->delcmd);
2074 ref->delcmd = NULL;
2075 }
2076 Tcl_DeleteCommandFromToken(ref->interp, ref->cmd);
2077 }
2078 Tcl_Free((char *)ref);
2079 ref = next;
2080 }
2081}
2082
2083 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01002084tcl_buffer_free(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002085{
2086 struct ref *reflist;
2087
2088#ifdef DYNAMIC_TCL
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01002089 if (!stubs_initialized) // Not using Tcl, nothing to do.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002090 return;
2091#endif
2092
Bram Moolenaare344bea2005-09-01 20:46:49 +00002093 reflist = (struct ref *)(buf->b_tcl_ref);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002094 if (reflist != &refsdeleted)
2095 {
Bram Moolenaare344bea2005-09-01 20:46:49 +00002096 buf->b_tcl_ref = (void *)&refsdeleted;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002097 tcldelallrefs(reflist);
Bram Moolenaare344bea2005-09-01 20:46:49 +00002098 buf->b_tcl_ref = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002099 }
2100}
2101
Bram Moolenaar071d4272004-06-13 20:20:40 +00002102 void
Bram Moolenaar68c2f632016-01-30 17:24:07 +01002103tcl_window_free(win_T *win)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002104{
2105 struct ref *reflist;
2106
2107#ifdef DYNAMIC_TCL
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01002108 if (!stubs_initialized) // Not using Tcl, nothing to do.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002109 return;
2110#endif
2111
Bram Moolenaare344bea2005-09-01 20:46:49 +00002112 reflist = (struct ref*)(win->w_tcl_ref);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002113 if (reflist != &refsdeleted)
2114 {
Bram Moolenaare344bea2005-09-01 20:46:49 +00002115 win->w_tcl_ref = (void *)&refsdeleted;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002116 tcldelallrefs(reflist);
Bram Moolenaare344bea2005-09-01 20:46:49 +00002117 win->w_tcl_ref = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002118 }
2119}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002120
Bram Moolenaar2ab2e862019-12-04 21:24:53 +01002121// The End