1 module zua.diagnostic; 2 import zua.parser.lexer; 3 import std.algorithm.sorting; 4 import std.array; 5 import std.typecons; 6 import std.algorithm; 7 8 /** Determines what type of diagnostic it represents */ 9 enum DiagnosticType { 10 Error, 11 Warning, 12 Info 13 } 14 15 /** Describes how a quickfix should be performed on a range */ 16 struct QuickfixRange { 17 18 /** Describes the range in the *original* document to modify */ 19 size_t from; 20 size_t to; /// ditto 21 22 /** The code to replace the given range with */ 23 string replaceWith; 24 25 /** Create a new quickfix range */ 26 this(size_t from, size_t to, string replaceWith) { 27 this.from = from; 28 this.to = to; 29 this.replaceWith = replaceWith; 30 } 31 32 /** Create a new quickfix range */ 33 this(Token token, string replaceWith) { 34 this.from = token.index; 35 this.to = token.index + token.rawValue.length; 36 this.replaceWith = replaceWith; 37 } 38 39 /** Create a new quickfix range */ 40 this(Token from, Token to, string replaceWith) { 41 this.from = from.index; 42 this.to = to.index + to.rawValue.length; 43 this.replaceWith = replaceWith; 44 } 45 46 int opCmp(ref const QuickfixRange other) const { 47 if (from < other.from) { 48 return -1; 49 } 50 else if (from > other.from) { 51 return 1; 52 } 53 else { 54 return 0; 55 } 56 } 57 58 bool opEquals(ref const QuickfixRange other) const { 59 return from == other.from; 60 } 61 62 size_t toHash() const nothrow @safe { 63 return typeid(from).getHash(&from); 64 } 65 66 } 67 68 /** Describes a quickfix option */ 69 struct Quickfix { 70 71 /** A message describing the fix */ 72 string message; 73 74 /** A list of quickfix range operations to apply. Ranges may not overlap */ 75 const(QuickfixRange)[] ranges; 76 77 } 78 79 /** Stores a diagnostic message */ 80 struct Diagnostic { 81 82 /** Determines what type of diagnostic it represents */ 83 DiagnosticType type; 84 85 /** The diagonstic message */ 86 string message; 87 88 /** The ranges of indices that this diagnostic message partains to */ 89 size_t[2][] ranges; 90 91 /** Provides a list of possible quickfixes */ 92 const(Quickfix)[] quickfix; 93 94 /** Create a new diagnostic message */ 95 this(DiagnosticType type, string message) { 96 this.type = type; 97 this.message = message; 98 } 99 100 /** Add a range of indices to this diagnostic message */ 101 void add(size_t from, size_t to) { 102 ranges ~= [from, to]; 103 } 104 105 /** Add a single index to this diagnostic message */ 106 void add(size_t at) { 107 ranges ~= [at, at]; 108 } 109 110 /** Add a single token to this diagnostic message */ 111 void add(Token token) { 112 add(token.index, token.index + token.rawValue.length); 113 } 114 115 /** Add a range of tokens to this diagnostic message, inclusive */ 116 void add(Token from, Token to) { 117 add(from.index, to.index + to.rawValue.length); 118 } 119 120 } 121 122 /** Return the modified source code after the application of a quickfix */ 123 string apply(Quickfix fix, string source) { 124 QuickfixRange[] ranges = fix.ranges.dup; 125 sort!"a > b"(ranges); 126 127 foreach (range; ranges) { 128 source = source[0..range.from] ~ range.replaceWith ~ source[range.to..$]; 129 } 130 131 return source; 132 } 133 134 /** Return the zero-indexed line number and local index from absolute index */ 135 auto decodeIndex(size_t index, string source) { 136 Tuple!(size_t, "line", size_t, "index") result; 137 138 index = min(source.length, index); 139 140 size_t currIndex = 0; 141 142 const lines = source.split('\n'); 143 foreach (lineNum; 0..lines.length) { 144 const line = lines[lineNum]; 145 if (index >= currIndex && index <= currIndex + line.length) { 146 result.line = lineNum; 147 result.index = index - currIndex; 148 break; 149 } 150 151 currIndex += line.length + 1; 152 } 153 154 return result; 155 } 156 157 unittest { 158 const string source = "ABCDEF"; 159 160 const Quickfix fix = { 161 message: "", 162 ranges: [QuickfixRange(1, 2, ".."), QuickfixRange(4, 5, "::")] 163 }; 164 165 assert(fix.apply(source) == "A..CD::F"); 166 }