Updated TODO.
[mp-5.x.git] / mp_file.mpsl
blobded3b93f01fea07617df697e365e7fa96fbfe6af
1 /*
3     Minimum Profit 5.x
4     A Programmer's Text Editor
6     File manipulation.
8     Copyright (C) 1991-2007 Angel Ortega <angel@triptico.com>
10     This program is free software; you can redistribute it and/or
11     modify it under the terms of the GNU General Public License
12     as published by the Free Software Foundation; either version 2
13     of the License, or (at your option) any later version.
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24     http://www.triptico.com
28 /* editor actions */
30 mp.actions['new']       = sub (d) {
31         d = mp.find_file_by_name(L("<unnamed>"));
33         if(d != -1)
34         {
35                 mp.active_i = d;
36                 d = mp.active();
37         }
38         else
39                 d = mp.new();
42 mp.actions['next']      = sub (d) { mp.next(); };
43 mp.actions['prev']      = sub (d) { mp.prev(); };
45 mp.actions['save_as']   = sub (d) {
47         local t;
49         if((t = mp.savefile(L("Save file as:"))) == NULL)
50                 return;
52         /* store new name */
53         d.name = t;
55         if(mp.long_op(mp.save, d) == -1)
56                 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
57         else
58                 mp.detect_syntax(d);
61 mp.actions['save']      = sub (d) {
63         /* name is <unnamed> or something similar; ask for one */
64         if(regex("/^<.+>$/", d.name))
65                 mp.actions.save_as(d);
66         else
67         if(mp.long_op(mp.save, d) == -1)
68                 mp.alert(sprintf(L("Error saving file: %s"), ERRNO));
71 mp.actions['close']     = sub (d) {
73         if(d.txt.mod)
74         {
75                 local r;
76                 r = mp.confirm(L("File has changed. Save changes?"));
78                 /* cancel? don't close */
79                 if(r == 0) return;
80                 if(r == 1) mp.actions.save(d);
81         }
83         mp.close();
86 mp.actions['exit']      = sub (d) {
88         local s;
90         if (mp.config.auto_sessions)
91                 mp.save_session();
93         if (mp.actions.close_all())
94                 mp.exit();
97 mp.actions['open']      = sub (d) {
99         local n;
101         if((n = mp.openfile(L("File to open:"))) != NULL && n ne "")
102                 if(mp.long_op(mp.open, n) == NULL && ERRNO != NULL)
103                         mp.alert(sprintf("Error opening '%s': %s", n, ERRNO));
106 mp.actions['revert']    = sub (d) {
107         /* save current name */
108         local p = d.name;
110         if(d.txt.mod)
111         {
112                 local r;
113                 r = mp.confirm(L("File has changed. Are you sure?"));
115                 /* cancel? don't close */
116                 if(r == 0 || r == 2) return;
117         }
119         mp.close();
120         if(mp.long_op(mp.open, p) == NULL && ERRNO != NULL)
121                 mp.alert(sprintf("Error opening '%s': %s", p, ERRNO));
124 mp.actions['open_config_file']  = sub (d) {
126         mp.open(HOMEDIR ~ ".mp.mpsl");
129 mp.actions['sync'] = sub (d) {
131         /* save all modified documents */
132         foreach(local d, grep(sub (e) { e.txt.mod; }, mp.docs))
133                 mp.actions.save(d);
136 mp.actions['exec_command']      = sub (d) {
138         local t = mp.form( [
139                 { 'label' => L("System command:"),
140                   'type' => 'text',
141                   'history' => 'system' }
142                 ]);
144         if(t != NULL)
145         {
146                 local cmd = t[0];
148                 /* does it start with a pipe? */
149                 if(regex('/^\|/', cmd))
150                 {
151                         local p;
153                         /* yes; current document should be fed to it */
154                         cmd = sregex('/^\|/', cmd, NULL);
156                         if((p = popen(cmd, "w")) != NULL)
157                         {
158                                 foreach(local l, mp.get_active_area(d))
159                                         write(p, l ~ mp.config.eol);
161                                 pclose(p);
162                         }
163                         else
164                                 mp.drv.alert(
165                                         sprintf(L("Error writing to command '%s'"), cmd));
166                 }
167                 else
168                 {
169                         /* no; execute command and insert into cursor */
170                         local p;
172                         if((p = popen(cmd, "r")) != NULL)
173                         {
174                                 local l;
176                                 mp.store_undo(d);
178                                 while((l = read(p)) != NULL)
179                                         mp.insert(d, l);
181                                 pclose(p);
182                         }
183                         else
184                                 mp.drv.alert(
185                                         sprintf(L("Error reading from command '%s'"), cmd));
186                 }
187         }
190 mp.actions['close_all'] = sub {
192         local s;
194         while (s = size(mp.docs)) {
195                 local doc = mp.docs[mp.active_i];
196                 
197                 /* close current document */
198                 mp.actions.close(doc);
200                 /* if the size of the list hasn't changed,
201                    action was cancelled, so don't exit */
202                 if(s == size(mp.docs)) return (0);
203         }
205         return (1);
208 /* default key bindings */
210 mp.keycodes['ctrl-n']           = 'next';
211 mp.keycodes['ctrl-o']           = 'open';
212 mp.keycodes['ctrl-q']           = 'exit';
213 mp.keycodes['ctrl-s']           = 'save';
214 mp.keycodes['ctrl-w']           = 'close';
216 mp.keycodes['close-window']     = 'exit';
218 /* action descriptions */
220 mp.actdesc['new']       = LL("New");
221 mp.actdesc['save']      = LL("Save...");
222 mp.actdesc['save_as']   = LL("Save as...");
223 mp.actdesc['next']      = LL("Next");
224 mp.actdesc['prev']      = LL("Previous");
225 mp.actdesc['open']      = LL("Open...");
226 mp.actdesc['exit']      = LL("Exit");
227 mp.actdesc['close']     = LL("Close");
228 mp.actdesc['revert']    = LL("Revert");
230 mp.actdesc['open_config_file']          = LL("Edit configuration file");
231 mp.actdesc['open_templates_file']       = LL("Edit templates file");
232 mp.actdesc['sync']                      = LL("Save modified texts");
233 mp.actdesc['exec_command']              = LL("Run system command...");
235 /* code */
237 sub mp.chomp(str)
238 /* chomps the end of file chars from a string */
240         sregex("/\r?\n$/", str, NULL);
244 sub mp.open_file_for_reading(filename)
245 /* the three-state file opening of text editors: open if possible,
246    fail on errors, create new if non-existent */
248         local f;
250         /* clear previous errors */
251         ERRNO = NULL;
253         if((f = open(filename, "rb")) == NULL)
254         {
255                 /* save ERRNO */
256                 local e = ERRNO;
258                 /* if a stat() can be done, it means the file
259                    exists and can't be open; this is an error.
260                    otherwise, it's a non-existent file, so not
261                    an error for a text editor */
262                 if(stat(filename) != NULL)
263                         ERRNO = e;
264                 else
265                         ERRNO = NULL;
266         }
268         return(f);
272 sub mp.save(doc)
273 /* saves a file */
275         local f;
276         local s = NULL;
277         local nl = 0;
279         /* if unlink before write is desired, do it */
280         if(mp.config.unlink && (s = stat(doc.name)) != NULL)
281                 unlink(doc.name);
283         if((f = open(doc.name, "wb")) == NULL)
284         {
285                 /* can't write? delete name */
286                 doc.name = L("<unnamed>");
287                 return(-1);
288         }
290         /* if the document has a password, save it encrypted */
291         if(doc.password)
292                 nl = mp.crypt1_save(f, doc.txt.lines, doc.password);
293         else
294         {
295                 /* save as a plain text file */
296                 foreach(local l, doc.txt.lines)
297                 {
298                         /* write a line separator if it's not the first line */
299                         if(nl) write(f, mp.config.eol);
301                         write(f, l);
302                         nl++;
303                 }
304         }
306         close(f);
308         doc.txt.mod = 0;
310         /* set back the permissions and ownership, if available */
311         if(s != NULL)
312         {
313                 chmod(doc.name, s[2]);
314                 chown(doc.name, s[4], s[5]);
315         }
317         return(nl);
321 sub mp.new(filename, lines)
322 /* creates a new document */
324         local doc, txt;
326         txt = {};
327         txt.x = 0;
328         txt.y = 0;
329         txt.vx = 0;
330         txt.vy = 0;
331         txt.lines = lines || [ '' ];
332         txt.mod = 0;
334         doc = {};
335         doc.name = filename || L("<unnamed>");
336         doc.txt = txt;
338         doc.undo = [];
339         doc.redo = [];
341         doc.syntax = NULL;
343         /* store in the list and set as active */
344         push(mp.docs, doc);
345         mp.active_i = size(mp.docs) - 1;
347         /* autodetect syntax */
348         mp.detect_syntax(doc);
350         return(doc);
354 sub mp.next()
355 /* rotates through the document list */
357         if(++mp.active_i == size(mp.docs))
358                 mp.active_i = 0;
362 sub mp.prev()
363 /* rotates through the document list, backwards */
365         if(--mp.active_i == -1)
366                 mp.active_i = size(mp.docs) - 1;
370 sub mp.close()
371 /* closes the active document */
373         local k = mp.active_i;
375         /* delete from the list */
376         adel(mp.docs, mp.active_i);
378         /* rotate if it was the last one */
379         if(mp.active_i == size(mp.docs))
380                 mp.active_i = 0;
384 sub mp.find_file_by_name(filename)
385 /* finds an open file by its name */
387         seek(map(sub(d) { d.name; }, mp.docs), filename);
391 sub mp.open(filename)
392 /* opens a new document (uses UI) */
394         local d = mp.find_file_by_name(filename);
396         /* looks first if the file is already open */
397         if(d != -1)
398         {
399                 mp.active_i = d;
400                 d = mp.active();
401         }
402         else
403         {
404                 local f, l, p;
406                 if((f = mp.open_file_for_reading(filename)) == NULL)
407                 {
408                         if(ERRNO != NULL)
409                                 return(NULL);
411                         /* file doesn't exist: new document */
412                 }
413                 else
414                 {
415                         if(mp.crypt1_detect(f))
416                         {
417                                 /* password needed; ask for it */
418                                 if((p = mp.form( [
419                                         { 'label'       => L("Password:"),
420                                           'type'        => 'password' }
421                                         ])) == NULL)
422                                 {
423                                         /* cancel? fail, but not on error */
424                                         ERRNO = NULL;
425                                         return(NULL);
426                                 }
428                                 /* get the password */
429                                 p = p[0];
431                                 /* an empty password is equal to cancellation */
432                                 if(p eq '') return(NULL);
434                                 /* and load the file */
435                                 l = mp.crypt1_load(f, p);
436                         }
437                         else
438                                 l = mp.plain_load(f);
440                         close(f);
441                 }
443                 d = mp.new(filename, l);
445                 /* does it have a password? store it */
446                 if(p != NULL) d.password = p;
447         }
449         return(d);