001: // Copyright (c) 2002, 2005 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.jemacs.buffer;
005:
006: import java.io.*;
007: import gnu.mapping.*;
008: import gnu.lists.*;
009: import gnu.text.*;
010: import gnu.commonlisp.lang.Symbols; // FIXME
011:
012: public abstract class Buffer extends AbstractSequence implements
013: CharSeq {
014: String name;
015: Path path;
016: String encoding;
017: //boolean modified;
018:
019: static Buffer current;
020:
021: public Marker pointMarker;
022: public Marker markMarker;
023:
024: /** Buffer-local variable bindings.
025: * Represented as pairs of (<code>Symbol</code>, value)-pairs:
026: * For an even integer <code>i</code>, if <code>localBindings[i]</code>
027: * is a <code>Symbol</code>, there is a buffer-local binding
028: * whose value is <code>localBindings{i+1]</code>. */
029: Object[] localBindings;
030:
031: /** List of modes active for this buffer, major mode first. */
032: Mode modes;
033:
034: /** Map buffer names to buffers. */
035: public static java.util.Hashtable buffers = new java.util.Hashtable(
036: 100);
037:
038: /** Map file names to buffer.s */
039: public static java.util.Hashtable fileBuffers = new java.util.Hashtable(
040: 100);
041:
042: EKeymap localKeymap;
043: public EKeymap[] activeKeymaps;
044: int activeLength;
045: // private EKeymap actual;
046: /* Count of initial Keymaps in activeKeymaps that have been eliminated,
047: * because of previous prefix keys. */
048: int eliminated = 0;
049:
050: public String getName() {
051: return name;
052: }
053:
054: public Path getPath() {
055: return path;
056: }
057:
058: public void setPath(Path path) {
059: this .path = path;
060: }
061:
062: public String getFileName() {
063: return path.toString();
064: }
065:
066: public void setFileName(String fname) {
067: String filename = getFileName();
068: if (filename != null && fileBuffers.get(filename) == this )
069: fileBuffers.remove(filename);
070: if (name != null && buffers.get(name) == this )
071: buffers.remove(name);
072: File file = new File(fname);
073: setPath(FilePath.valueOf(file));
074: name = generateNewBufferName(file.getName());
075: buffers.put(name, this );
076: fileBuffers.put(filename, this );
077: redrawModeline();
078: }
079:
080: public abstract CharSeq getStringContent();
081:
082: /**
083: * @see gnu.lists.CharSeq#charAt(int)
084: */
085: public char charAt(int index) {
086: return getStringContent().charAt(index);
087: }
088:
089: /**
090: * @see gnu.lists.CharSeq#setCharAt(int, char)
091: */
092: public void setCharAt(int index, char ch) {
093: getStringContent().setCharAt(index, ch);
094: }
095:
096: /**
097: * @see gnu.lists.CharSeq#fill(char)
098: */
099: public void fill(char value) {
100: getStringContent().fill(value);
101: }
102:
103: /**
104: * @see gnu.lists.CharSeq#fill(int, int, char)
105: */
106: public void fill(int fromIndex, int toIndex, char value) {
107: getStringContent().fill(fromIndex, toIndex, value);
108: }
109:
110: /**
111: * @see gnu.lists.CharSeq#getChars(int, int, char[], int)
112: */
113: public void getChars(int srcBegin, int srcEnd, char[] dst,
114: int dstBegin) {
115: getStringContent().getChars(srcBegin, srcEnd, dst, dstBegin);
116: }
117:
118: /* #ifdef use:java.lang.CharSequence */
119: public CharSequence subSequence(int start, int end) {
120: return getStringContent().subSequence(start, end);
121: }
122:
123: /* #endif */
124: /* #ifdef JAVA5 */
125: // /**
126: // * @see gnu.lists.CharSeq#writeTo(int, int, Appendable)
127: // */
128: // public void writeTo(int start, int count, Appendable dest)
129: // throws java.io.IOException
130: // {
131: // getStringContent().writeTo(start, count, dest);
132: // }
133: // public void writeTo(Appendable dest)
134: // throws java.io.IOException
135: // {
136: // writeTo(0, length(), dest);
137: // }
138: /* #else */
139: /**
140: * @see gnu.lists.CharSeq#writeTo(int, int, java.io.Writer)
141: */
142: public void writeTo(int start, int count, java.io.Writer dest)
143: throws java.io.IOException {
144: getStringContent().writeTo(start, count, dest);
145: }
146:
147: public void writeTo(java.io.Writer str) throws java.io.IOException {
148: writeTo(0, length(), str);
149: }
150:
151: /* #endif */
152:
153: /**
154: * @see gnu.lists.CharSeq#consume(int, int, gnu.lists.Consumer)
155: */
156: public void consume(int start, int count, gnu.lists.Consumer out) {
157: getStringContent().consume(start, count, out);
158: }
159:
160: public static Buffer findFile(String fname) {
161: Buffer buffer = (Buffer) fileBuffers.get(fname);
162: if (buffer == null) {
163: buffer = EToolkit.getInstance().newBuffer(null);
164: buffer.setFileName(fname);
165: buffer.encoding = System.getProperty("file.encoding",
166: "UTF8");
167: try {
168: Reader in = new InputStreamReader(new FileInputStream(
169: fname), buffer.encoding);
170: buffer.insertFile(in);
171: in.close();
172: } catch (java.io.FileNotFoundException ex) {
173: Signal.message("New file");
174: } catch (Exception ex) {
175: throw new RuntimeException("error reading file \""
176: + fname + "\": " + ex);
177: }
178: }
179: return buffer;
180: }
181:
182: public static Buffer getBuffer(String name) {
183: return (Buffer) buffers.get(name);
184: }
185:
186: public static Buffer coerceBuffer(Object buf) {
187: if (buf instanceof Buffer)
188: return (Buffer) buf;
189: return getBuffer(buf.toString());
190: }
191:
192: public static String generateNewBufferName(String start) {
193: Buffer buf = getBuffer(start);
194: if (buf == null)
195: return start;
196: int len = start.length();
197: StringBuffer sbuf = new StringBuffer(len + 5);
198: sbuf.append(start);
199: sbuf.append('<');
200: for (int i = 2;; i++) {
201: sbuf.append(i);
202: sbuf.append('>');
203: String name = sbuf.toString();
204: buf = getBuffer(name);
205: if (buf == null)
206: return name;
207: sbuf.setLength(len + 1);
208: }
209: }
210:
211: public abstract void redrawModeline();
212:
213: public Buffer(String name) {
214: this .name = name;
215:
216: activeKeymaps = new EKeymap[6];
217: activeLength = 1;
218: activeKeymaps[0] = EKeymap.globalKeymap;
219: }
220:
221: public int checkMark() {
222: return markMarker.getOffset();
223: }
224:
225: public static Buffer getCurrent() {
226: return current;
227: }
228:
229: public static void setCurrent(Buffer buffer) {
230: current = buffer;
231: }
232:
233: public int getDot() {
234: return pointMarker.getOffset();
235: }
236:
237: public int getPoint() {
238: return 1 + getDot();
239: }
240:
241: public void setDot(int i) {
242: if (i > maxDot())
243: throw new Error("set dot to " + i + " max:" + maxDot());
244: pointMarker.set(this , i);
245: }
246:
247: public final void setPoint(int i) {
248: setDot(i - 1);
249: }
250:
251: public int minDot() {
252: return 0;
253: }
254:
255: public abstract int getLength();
256:
257: public final int length() {
258: return getLength();
259: }
260:
261: public abstract int maxDot();
262:
263: public void forwardChar(int i) {
264: pointMarker.forwardChar(i);
265: }
266:
267: public void backwardChar(int i) {
268: pointMarker.backwardChar(i);
269: }
270:
271: public String toString() {
272: return "#<buffer \"" + name + "\">";
273: }
274:
275: /** Insert count copies of ch at Pos ipos. */
276: /*
277: public void insert (char ch, int count, Object style, int ipos)
278: {
279: }
280: */
281:
282: /** Insert string with given style at position pair. */
283: public abstract void insert(String string, Object style, int ipos);
284:
285: /** Insert character with given style at position pair. */
286: public void insert(char[] chars, int offset, int count,
287: Object style, int ipos) {
288: insert(new String(chars, offset, count), style, ipos);
289: }
290:
291: public void insertAll(Object[] values, Object style) {
292: int len = values.length;
293: for (int i = 0; i < len; i++) {
294: Object value = values[i];
295: if (value instanceof Char)
296: insert(((Char) value).charValue(), 1, style);
297: else
298: pointMarker.insert(value.toString(), style);
299: }
300: }
301:
302: public void insert(String string, Object style) {
303: pointMarker.insert(string, style);
304: }
305:
306: public void insert(Object value, Object style) {
307: if (value instanceof Char)
308: insert(((Char) value).charValue(), 1, style);
309: else
310: pointMarker.insert(value.toString(), style);
311: }
312:
313: /** Insert count copies of ch at point. */
314: public void insert(char ch, int count) {
315: pointMarker.insert(ch, count, null);
316: }
317:
318: /** Insert count copies of ch at point. */
319: public void insert(char ch, int count, Object style) {
320: pointMarker.insert(ch, count, style);
321: }
322:
323: public void removeChar(int count) {
324: pointMarker.removeChar(count);
325: }
326:
327: public abstract void removeAll();
328:
329: public Marker getPointMarker(boolean share) {
330: return share ? pointMarker : new Marker(pointMarker);
331: }
332:
333: public Marker getMarkMarker(boolean force) {
334: return markMarker;
335: }
336:
337: /** Convert an Emacs position (Marker, or 1-origin integer)
338: * to a (0-origin) buffer offset. */
339: public int positionToOffset(Object position) {
340: if (position instanceof Number) {
341: int min = minDot();
342: int max = maxDot();
343: int goal = ((Number) position).intValue() - 1;
344: return goal < min ? min : goal > max ? max : goal;
345: }
346: return ((Marker) position).getOffset();
347: }
348:
349: public abstract void insertFile(Reader in) throws Exception;
350:
351: public abstract void save(Writer out) throws Exception;
352:
353: public void save() {
354: try {
355: if (encoding == null)
356: encoding = System.getProperty("file.encoding", "UTF8");
357: Writer out = new OutputStreamWriter(
358: path.openOutputStream(), encoding);
359: save(out);
360: out.close();
361: } catch (Exception ex) {
362: throw new RuntimeException("error save-buffer: " + ex);
363: }
364: }
365:
366: public void insertFile(String filename) {
367: try {
368: if (encoding == null)
369: encoding = System.getProperty("file.encoding", "UTF8");
370: Reader in = new InputStreamReader(new FileInputStream(
371: filename), encoding);
372: insertFile(in);
373: in.close();
374: } catch (Exception ex) {
375: throw new RuntimeException("error reading file \""
376: + filename + "\": " + ex);
377: }
378: }
379:
380: int tabWidth = 8;
381:
382: public int charWidth(char ch, int column) {
383: if (ch < 0x3000) {
384: // Combining forma should probably be 0.
385: if (ch < ' ') {
386: if (ch == '\t')
387: return (((column + tabWidth) / tabWidth) * tabWidth)
388: - column;
389: return 0;
390: }
391: } else {
392: if (ch < 0xD800 // CJK Ideographs
393: || (ch >= 0xFF01 && ch <= 0xFF5E) // Fullwidth ASCII.
394: || (ch >= 0xFFe0 && ch <= 0xFFE6)) // Fullwidth punctuation.
395: return 2;
396: if (ch < 0xE000)
397: return 0; // Surrogates.
398: }
399: return 1;
400: }
401:
402: public int countColumns(char[] chars, int start, int count,
403: int initial) {
404: while (--count >= 0)
405: initial += charWidth(chars[start++], initial);
406: return initial;
407: }
408:
409: public int currentColumn() {
410: return currentColumn(getDot());
411: }
412:
413: /** Return the column number at a specified offset. */
414: public int currentColumn(int offset) {
415: try {
416: int lineStart = lineStartOffset(offset);
417: InPort port = openReader(lineStart, offset - lineStart);
418: int column = 0;
419: while (port.read() >= 0) {
420: // Subtract one from pos, to undo the read we just did.
421: int start = port.pos - 1;
422: column = countColumns(port.buffer, start, port.limit
423: - start, column);
424: port.pos = port.limit;
425: }
426: return column;
427: } catch (java.io.IOException ex) {
428: throw new WrappedException(ex);
429: }
430: }
431:
432: public int moveToColumn(int column, boolean force) {
433: return pointMarker.moveToColumn(column, force);
434: }
435:
436: public abstract int lineStartOffset(int offset);
437:
438: public int lineStartOffset() {
439: return lineStartOffset(getDot());
440: }
441:
442: /** Search in BUF for COUNT instances of the character TARGET between START and END.
443: * If COUNT is positive, search forwards; END must be >= START.
444: * If COUNT is negative, search backwards for the -COUNTth instance;
445: * END must be <= START.
446: * If COUNT is zero, do anything you please; run rogue, for all I care.
447: * If END is zero, use beginning or end of (FIXME: accessible part of)
448: * the buffer, as appropriate for the direction indicated by COUNT.
449: *
450: * If we find COUNT instances, SHORTAGE is zero, and return the
451: * position after the COUNTth match. Note that for reverse motion
452: * this is not the same as the usual convention for Emacs motion commands.
453:
454: * If we don't find COUNT instances before reaching END, set SHORTAGE
455: * to the number of TARGETs left unfound, and return (shortage<<32|END).
456: * @return (SHORTAGE<<32|POS)
457: */
458: public abstract long scan(char target, int start, int end,
459: int count, boolean allowQuit);
460:
461: /** Find the position a give number of lines forward or backward.
462: * A side-effect-free version of Emacs's forward-line function.
463: * @param lines number of lines forward (or backward if negative)
464: * @param start initial position (buffer offset)
465: * @return (SHORTAGE<<32|POS)
466: */
467: public final long forwardLine(int lines, int start) {
468: boolean neg = lines <= 0;
469: long scanned = scan('\n', start, 0, lines - (neg ? 1 : 0), true);
470: int shortage = (int) (scanned >> 32);
471: int pos = (int) scanned;
472: if (shortage > 0
473: && (neg || (maxDot() > minDot() && pos != start && charAt(pos - 1) != '\n')))
474: shortage--;
475: return ((long) (neg ? -shortage : shortage) << 32) | (long) pos;
476: }
477:
478: public int forwardLine(int lines) {
479: long value = forwardLine(lines, getDot());
480: setDot((int) value);
481: return (int) (value >> 32);
482: }
483:
484: public EWindow display(boolean notThisWindow, EFrame frame) {
485: if (frame == null)
486: frame = EFrame.getSelectedFrame();
487: EWindow selected = frame.getSelectedWindow();
488: EWindow window = frame.otherWindow(1);
489: if (selected == window && notThisWindow)
490: window = selected.split(-1, false);
491: window.setBuffer(this );
492: return window;
493: }
494:
495: /*
496: public Element createLeafElement(Element parent, AttributeSet attributes,
497: int p0, int p1)
498: {
499: p0 = content.createPosition(p0, p0!=0);
500: p1 = content.createPosition(p1, true);
501: return new Leaf(this, parent, attributes, p0, p1);
502: }
503: */
504:
505: /**
506: * @param all true if make-variable-buffer-local,
507: * false if make-local-variable FIXME
508: */
509: public static void makeBufferLocal(Object symbol, boolean all) {
510: BufferLocal.make(Symbols.getSymbol(symbol), all);
511: }
512:
513: public EKeymap getLocalKeymap() {
514: return localKeymap;
515: }
516:
517: public void setLocalKeymap(EKeymap map) {
518: // First remove the old local map.
519: if (localKeymap != null) {
520: activeKeymaps[activeLength - 2] = activeKeymaps[activeLength - 1];
521: activeLength--;
522: localKeymap = null;
523: }
524: if (map != null) {
525: activeKeymaps[activeLength] = activeKeymaps[activeLength - 1];
526: activeKeymaps[activeLength - 1] = map;
527: activeLength++;
528: localKeymap = map;
529: }
530: }
531:
532: public abstract InPort openReader(int start, int count);
533:
534: public abstract long savePointMark();
535:
536: public abstract void restorePointMark(long pointMark);
537:
538: /**
539: * This is intended for Runnable's that may affect the state of the buffer.
540: * The implementation should make shure that the GUI is properly updated before
541: * control returns
542: *
543: * @param doRun
544: */
545: public abstract void invoke(Runnable doRun);
546:
547: }
|