aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/editor/TextBuffer.java
blob: e6cf211d481bc631033819395aa66353036ada11 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

package com.yahoo.vespa.hosted.node.admin.task.util.editor;

import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author hakon
 */
interface TextBuffer {
    // INTERFACE TO IMPLEMENT BY CONCRETE CLASS

    /** Get the version of the buffer - edits increment the version. */
    Version getVersion();

    /** Return the text as a single String (likely) with embedded newlines. */
    String getString();

    /** Return the maximum line index (the minimum line index is 0). */
    int getMaxLineIndex();

    /** @param lineIndex must be in in {@code [0, getMaxLineIndex()]} */
    String getLine(int lineIndex);

    /** Insert the possibly multi-line text at position and return the end position. */
    Position write(Position position, String text);

    /** Delete everything. */
    void clear();

    /** Delete range. */
    void delete(Position start, Position end);

    // DERIVED IMPLEMENTATION

    /**
     * Return the Position closest to {@code position} which is in the range
     * {@code [getStartOfText(), getEndOfText()]}.
     */
    default Position getValidPositionClosestTo(Position position) {
        if (position.isBefore(getStartOfText())) {
            return getStartOfText();
        } else if (position.isAfter(getEndOfText())) {
            return getEndOfText();
        } else {
            return position;
        }
    }

    default String getLine(Position position) { return getLine(position.lineIndex()); }

    default String getLinePrefix(Position position) {
        return getLine(position.lineIndex()).substring(0, position.columnIndex());
    }

    default String getLineSuffix(Position position) {
        return getLine(position.lineIndex()).substring(position.columnIndex());
    }

    default String getSubstring(Position start, Position end) {
        if (start.lineIndex() < end.lineIndex()) {
            StringBuilder builder = new StringBuilder(getLineSuffix(start));
            for (int i = start.lineIndex() + 1; i < end.lineIndex(); ++i) {
                builder.append('\n');
                builder.append(getLine(i));
            }
            return builder.append('\n').append(getLinePrefix(end)).toString();
        } else if (start.lineIndex() == end.lineIndex() && start.columnIndex() <= end.columnIndex()) {
            return getLine(start).substring(start.columnIndex(), end.columnIndex());
        }

        throw new IllegalArgumentException(
                "Bad range [" + start.coordinateString() + "," + end.coordinateString() + ">");
    }

    default Position getStartOfText() { return Position.start(); } // aka (0,0)

    default Position getEndOfText() {
        int maxLineIndex = getMaxLineIndex();
        return new Position(maxLineIndex, getLine(maxLineIndex).length());
    }

    default Position getStartOfLine(Position position) {
        return new Position(position.lineIndex(), 0);
    }

    default Position getEndOfLine(Position position) {
        return new Position(position.lineIndex(), getLine(position).length());
    }

    default Position getStartOfNextLine(Position position) {
        if (position.lineIndex() < getMaxLineIndex()) {
            return new Position(position.lineIndex() + 1, 0);
        } else {
            return getEndOfText();
        }
    }

    default Position getStartOfPreviousLine(Position position) {
        int lineIndex = position.lineIndex();
        if (lineIndex > 0) {
            return new Position(lineIndex - 1, 0);
        } else {
            return getStartOfText();
        }
    }

    default Position forward(Position position, int length) {
        int lineIndex = position.lineIndex();
        int columnIndex = position.columnIndex();

        int offsetLeft = length;
        do {
            String line = getLine(lineIndex);
            int columnIndexWithInfiniteLine = columnIndex + offsetLeft;
            if (columnIndexWithInfiniteLine <= line.length()) {
                return new Position(lineIndex, columnIndexWithInfiniteLine);
            } else if (lineIndex >= getMaxLineIndex()) {
                // End of text
                return new Position(lineIndex, line.length());
            }

            offsetLeft -= line.length() - columnIndex;

            // advance past newline
            --offsetLeft;
            ++lineIndex;
            columnIndex = 0;

            // At this point: offsetLeft is guaranteed to be >= 0, and lineIndex <= max line index
        } while (true);
    }

    default Position backward(Position position, int length) {
        int lineIndex = position.lineIndex();
        int columnIndex = position.columnIndex();

        int offsetLeft = length;
        do {
            int columnIndexWithInfiniteLine = columnIndex - offsetLeft;
            if (columnIndexWithInfiniteLine >= 0) {
                return new Position(lineIndex, columnIndexWithInfiniteLine);
            } else if (lineIndex <= 0) {
                // Start of text
                return new Position(0, 0);
            }

            offsetLeft -= columnIndex;

            // advance past newline
            --offsetLeft;
            --lineIndex;
            columnIndex = getLine(lineIndex).length();

            // At this point: offsetLeft is guaranteed to be <= 0, and lineIndex >= 0
        } while (true);
    }

    default Optional<Match> findForward(Position startPosition, Pattern pattern) {
        for (Position position = startPosition; ; position = getStartOfNextLine(position)) {
            String line = getLine(position);
            Matcher matcher = pattern.matcher(line);
            if (matcher.find(position.columnIndex())) {
                return Optional.of(new Match(position.lineIndex(), line, matcher));
            }

            if (position.lineIndex() == getMaxLineIndex()) {
                // search failed - no lines matched
                return Optional.empty();
            }
        }
    }
}