Bladeren bron

- Moved token handling out of the code editor

- Fixed some bugs in the code editor
Stan Hebben 6 jaren geleden
bovenliggende
commit
c05bc0d7b1

+ 61
- 236
IDE/src/main/java/org/openzen/zenscript/ide/ui/view/editor/SourceEditor.java Bestand weergeven

@@ -6,9 +6,6 @@
6 6
 package org.openzen.zenscript.ide.ui.view.editor;
7 7
 
8 8
 import java.io.IOException;
9
-import java.util.ArrayList;
10
-import java.util.Iterator;
11
-import java.util.List;
12 9
 import java.util.Timer;
13 10
 import java.util.TimerTask;
14 11
 import org.openzen.drawablegui.DCanvas;
@@ -24,6 +21,7 @@ import org.openzen.drawablegui.DKeyEvent.KeyCode;
24 21
 import org.openzen.drawablegui.DMouseEvent;
25 22
 import org.openzen.drawablegui.DRectangle;
26 23
 import org.openzen.drawablegui.DTransform2D;
24
+import org.openzen.drawablegui.listeners.ListenerHandle;
27 25
 import org.openzen.drawablegui.live.LiveObject;
28 26
 import org.openzen.drawablegui.live.SimpleLiveObject;
29 27
 import org.openzen.zenscript.ide.host.IDESourceFile;
@@ -43,6 +41,8 @@ public class SourceEditor implements DComponent {
43 41
 	private final LiveObject<DDimensionPreferences> dimensionPreferences = new SimpleLiveObject<>(new DDimensionPreferences(0, 0));
44 42
 	private final String tab = "    ";
45 43
 	private final IDESourceFile sourceFile;
44
+	private final TokenModel tokens;
45
+	private final ListenerHandle<TokenModel.Listener> tokenListener;
46 46
 	
47 47
 	private DRectangle bounds;
48 48
 	private DDrawingContext context;
@@ -50,7 +50,6 @@ public class SourceEditor implements DComponent {
50 50
 	private int textLineHeight;
51 51
 	private int fullLineHeight;
52 52
 	private int selectionLineHeight;
53
-	private final List<Line> lines = new ArrayList<>();
54 53
 	
55 54
 	private int lineBarWidth;
56 55
 	private CursorPosition cursorStart = null;
@@ -65,6 +64,8 @@ public class SourceEditor implements DComponent {
65 64
 	
66 65
 	public SourceEditor(IDESourceFile sourceFile) {
67 66
 		this.sourceFile = sourceFile;
67
+		tokens = new TokenModel(sourceFile.getName(), tab.length());
68
+		tokenListener = tokens.addListener(new TokenListener());
68 69
 		
69 70
 		blink.scheduleAtFixedRate(new TimerTask() {
70 71
 			@Override
@@ -74,7 +75,8 @@ public class SourceEditor implements DComponent {
74 75
 		}, 300, 300);
75 76
 		
76 77
 		try {
77
-			parse();
78
+			TokenParser<ZSToken, ZSTokenType> parser = ZSTokenParser.createRaw(sourceFile.getName(), new ReaderCharReader(sourceFile.read()), tab.length());
79
+			tokens.set(parser);
78 80
 		} catch (IOException ex) {
79 81
 			ex.printStackTrace();
80 82
 		}
@@ -88,7 +90,7 @@ public class SourceEditor implements DComponent {
88 90
 		fullLineHeight = textLineHeight + fontMetrics.getLeading() + style.extraLineSpacing;
89 91
 		selectionLineHeight = textLineHeight + style.selectionPaddingTop + style.selectionPaddingBottom;
90 92
 		
91
-		dimensionPreferences.setValue(new DDimensionPreferences(0, fullLineHeight * lines.size()));
93
+		dimensionPreferences.setValue(new DDimensionPreferences(0, fullLineHeight * tokens.getLineCount()));
92 94
 	}
93 95
 
94 96
 	@Override
@@ -111,7 +113,7 @@ public class SourceEditor implements DComponent {
111 113
 		DIRectangle canvasBounds = canvas.getBounds();
112 114
 		canvas.fillRectangle(bounds.x, bounds.y, bounds.width, bounds.height, 0xFFFFFFFF);
113 115
 		
114
-		lineBarWidth = Math.max(50, fontMetrics.getWidth(Integer.toString(lines.size()))) + 5;
116
+		lineBarWidth = Math.max(50, fontMetrics.getWidth(Integer.toString(tokens.getLineCount()))) + 5;
115 117
 		canvas.fillRectangle(bounds.x, bounds.y, lineBarWidth, bounds.height, 0xFFE9E8E2);
116 118
 		canvas.strokePath(tracer -> {
117 119
 			tracer.moveTo(bounds.x + lineBarWidth, bounds.y);
@@ -148,14 +150,14 @@ public class SourceEditor implements DComponent {
148 150
 		
149 151
 		int y = bounds.y + style.selectionPaddingTop;
150 152
 		int lineIndex = 1;
151
-		for (Line line : lines) {
153
+		for (TokenLine line : tokens.getLines()) {
152 154
 			if (y + textLineHeight  >= canvasBounds.y && y < canvasBounds.y + canvasBounds.height) {
153 155
 				String lineNumber = Integer.toString(lineIndex);
154 156
 				int lineNumberX = x - 15 - (int)canvas.measureTextLength(font, lineNumber);
155 157
 				canvas.drawText(font, 0xFFA0A0A0, lineNumberX, y + fontMetrics.getAscent(), lineNumber);
156 158
 
157 159
 				int lineX = x;
158
-				for (ZSToken token : line.tokens) {
160
+				for (ZSToken token : line.getTokens()) {
159 161
 					String content = getDisplayContent(token);
160 162
 					canvas.drawText(font, TokenClass.get(token.type).color, lineX, y + fontMetrics.getAscent(), content);
161 163
 					lineX += canvas.measureTextLength(font, content);
@@ -248,19 +250,19 @@ public class SourceEditor implements DComponent {
248 250
 					int line = cursorEnd.line - 1;
249 251
 					CursorPosition position = new CursorPosition(
250 252
 							line,
251
-							Math.min(lines.get(line).length, cursorEnd.offset));
253
+							Math.min(tokens.getLineLength(line), cursorEnd.offset));
252 254
 					setCursor(shift ? cursorStart : position, position);
253 255
 				}
254 256
 				break;
255 257
 			case DOWN:
256
-				if (cursorEnd == null || cursorEnd.line >= lines.size() - 1)
258
+				if (cursorEnd == null || cursorEnd.line >= tokens.getLineCount() - 1)
257 259
 					return;
258 260
 				
259 261
 				{
260 262
 					int line = cursorEnd.line + 1;
261 263
 					CursorPosition position = new CursorPosition(
262 264
 							line,
263
-							Math.min(lines.get(line).length, cursorEnd.offset));
265
+							Math.min(tokens.getLineLength(line), cursorEnd.offset));
264 266
 					setCursor(shift ? cursorStart : position, position);
265 267
 				}
266 268
 				break;
@@ -272,7 +274,7 @@ public class SourceEditor implements DComponent {
272 274
 					CursorPosition position;
273 275
 					if (cursorEnd.offset == 0) {
274 276
 						int line = cursorEnd.line - 1;
275
-						position = new CursorPosition(line, lines.get(line).length);
277
+						position = new CursorPosition(line, tokens.getLineLength(line));
276 278
 					} else {
277 279
 						position = new CursorPosition(cursorEnd.line, cursorEnd.offset - 1);
278 280
 					}
@@ -280,12 +282,12 @@ public class SourceEditor implements DComponent {
280 282
 				}
281 283
 				break;
282 284
 			case RIGHT:
283
-				if (cursorEnd == null || (cursorEnd.offset == lines.get(cursorEnd.line).length && cursorEnd.line >= lines.size() - 1))
285
+				if (cursorEnd == null || (cursorEnd.offset == tokens.getLineLength(cursorEnd.line) && cursorEnd.line >= tokens.getLineCount() - 1))
284 286
 					return;
285 287
 				
286 288
 				{
287 289
 					CursorPosition position;
288
-					if (cursorEnd.offset == lines.get(cursorEnd.line).length) {
290
+					if (cursorEnd.offset == tokens.getLineLength(cursorEnd.line)) {
289 291
 						position = new CursorPosition(cursorEnd.line + 1, 0);
290 292
 					} else {
291 293
 						position = new CursorPosition(cursorEnd.line, cursorEnd.offset + 1);
@@ -303,7 +305,7 @@ public class SourceEditor implements DComponent {
303 305
 				newline();
304 306
 				break;
305 307
 			case TAB:
306
-				type(tab);
308
+				type("\t");
307 309
 				break;
308 310
 			default:
309 311
 				if (e.character == DKeyEvent.CHAR_UNDEFINED)
@@ -326,38 +328,22 @@ public class SourceEditor implements DComponent {
326 328
 	}
327 329
 	
328 330
 	private void save() {
329
-		String content = contentToString();
331
+		String content = tokens.toString();
330 332
 		sourceFile.update(content);
331 333
 	}
332 334
 	
333
-	private String contentToString() {
334
-		StringBuilder result = new StringBuilder();
335
-		for (int i = 0; i < lines.size(); i++) {
336
-			if (i > 0)
337
-				result.append("\n");
338
-			
339
-			Line line = lines.get(i);
340
-			for (ZSToken token : line.tokens) {
341
-				result.append(token.content);
342
-			}
343
-		}
344
-		return result.toString();
345
-	}
346
-	
347 335
 	private void delete() {
348
-		Line line = lines.get(cursorEnd.line);
349
-		if (cursorEnd.offset == line.length) {
336
+		TokenLine line = tokens.getLine(cursorEnd.line);
337
+		if (cursorEnd.offset == line.length()) {
350 338
 			// merge 2 lines
351
-			if (cursorEnd.line == lines.size() - 1)
339
+			if (cursorEnd.line == tokens.getLineCount() - 1)
352 340
 				return;
353 341
 			
354
-			line.merge(cursorEnd.line, lines.get(cursorEnd.line + 1));
355
-			lines.remove(cursorEnd.line + 1);
356
-			onLinesUpdated();
342
+			tokens.deleteNewline(cursorEnd.line);
357 343
 			return;
358 344
 		}
359 345
 		
360
-		line.delete(cursorEnd.line, cursorEnd.offset);
346
+		tokens.deleteCharacter(cursorEnd.line, cursorEnd.offset);
361 347
 		repaintLine(cursorEnd.line);
362 348
 	}
363 349
 	
@@ -366,47 +352,42 @@ public class SourceEditor implements DComponent {
366 352
 			if (cursorEnd.line == 0)
367 353
 				return;
368 354
 			
369
-			int length = lines.get(cursorEnd.line - 1).length;
370
-			lines.get(cursorEnd.line - 1).merge(cursorEnd.line - 1, lines.get(cursorEnd.line));
371
-			lines.remove(cursorEnd.line);
372
-			onLinesUpdated();
355
+			int length = tokens.getLineLength(cursorEnd.line - 1);
356
+			tokens.deleteNewline(cursorEnd.line - 1);
373 357
 			
374 358
 			CursorPosition position = new CursorPosition(cursorEnd.line - 1, length);
375 359
 			setCursor(position, position);
376 360
 			return;
377 361
 		}
378 362
 		
379
-		lines.get(cursorEnd.line).delete(cursorEnd.line, cursorEnd.offset - 1);
363
+		tokens.deleteCharacter(cursorEnd.line, cursorEnd.offset - 1);
380 364
 		CursorPosition position = new CursorPosition(cursorEnd.line, cursorEnd.offset - 1);
381 365
 		setCursor(position, position);
382
-		repaintLine(cursorEnd.line);
383 366
 	}
384 367
 	
385 368
 	private void type(String value) {
386
-		lines.get(cursorEnd.line).insert(cursorEnd.line, cursorEnd.offset, value);
369
+		tokens.insert(cursorEnd.line, cursorEnd.offset, value);
387 370
 		CursorPosition position = new CursorPosition(cursorEnd.line, cursorEnd.offset + value.length());
388 371
 		setCursor(position, position);
389
-		repaintLine(cursorEnd.line);
390 372
 	}
391 373
 	
392 374
 	private void newline() {
393
-		lines.get(cursorEnd.line).split(cursorEnd.line, cursorEnd.offset);
394
-		onLinesUpdated();
395
-		
396
-		CursorPosition position = new CursorPosition(cursorEnd.line + 1, 0);
375
+		String indent = tokens.getLine(cursorEnd.line).getIndent();
376
+		tokens.insert(cursorEnd.line, cursorEnd.offset, "\n" + indent);
377
+		CursorPosition position = new CursorPosition(cursorEnd.line + 1, indent.length());
397 378
 		setCursor(position, position);
398 379
 	}
399 380
 	
400 381
 	private TokenPosition getTokenAt(CursorPosition position) {
401
-		Line line = lines.get(position.line);
382
+		TokenLine line = tokens.getLine(position.line);
402 383
 		int offset = 0;
403
-		for (ZSToken token : line.tokens) {
384
+		for (ZSToken token : line.getTokens()) {
404 385
 			if (offset + token.content.length() > position.offset)
405 386
 				return new TokenPosition(token, position.offset - offset);
406 387
 			offset += token.content.length();
407 388
 		}
408 389
 		
409
-		return new TokenPosition(line.tokens.get(line.tokens.size() - 1), position.offset - offset);
390
+		return new TokenPosition(line.getLastToken(), position.offset - offset);
410 391
 	}
411 392
 	
412 393
 	private class TokenPosition {
@@ -433,6 +414,9 @@ public class SourceEditor implements DComponent {
433 414
 	}
434 415
 	
435 416
 	private void repaintLine(int line) {
417
+		if (bounds == null)
418
+			return;
419
+		
436 420
 		context.repaint(bounds.x, lineToY(line), bounds.width, selectionLineHeight);
437 421
 	}
438 422
 	
@@ -455,20 +439,20 @@ public class SourceEditor implements DComponent {
455 439
 		
456 440
 		if (line < 0)
457 441
 			line = 0;
458
-		if (line >= lines.size())
459
-			line = lines.size() - 1;
442
+		if (line >= tokens.getLineCount())
443
+			line = tokens.getLineCount() - 1;
460 444
 		
461 445
 		return new CursorPosition(line, offset);
462 446
 	}
463 447
 	
464 448
 	private int xToOffset(int lineIndex, int x) {
465
-		if (lineIndex < 0 || lineIndex >= lines.size())
449
+		if (lineIndex < 0 || lineIndex >= tokens.getLineCount())
466 450
 			return 0;
467 451
 		
468
-		Line line = lines.get(lineIndex);
452
+		TokenLine line = tokens.getLine(lineIndex);
469 453
 		int lineX = bounds.x + lineBarWidth + 10;
470 454
 		int offset = 0;
471
-		for (ZSToken token : line.tokens) {
455
+		for (ZSToken token : line.getTokens()) {
472 456
 			String content = getDisplayContent(token);
473 457
 			int tokenWidth = fontMetrics.getWidth(content);
474 458
 			if (lineX + tokenWidth > x) {
@@ -490,7 +474,7 @@ public class SourceEditor implements DComponent {
490 474
 	
491 475
 	private int getStringIndexForPixels(String str, int pixels) {
492 476
 		int previousX = 0;
493
-		for (int i = 1; i < str.length(); i++) {
477
+		for (int i = 1; i <= str.length(); i++) {
494 478
 			int currentX = fontMetrics.getWidth(str, 0, i);
495 479
 			if (currentX >= pixels)
496 480
 				return (pixels - previousX < currentX - pixels) ? i - 1 : i;
@@ -510,13 +494,13 @@ public class SourceEditor implements DComponent {
510 494
 	}
511 495
 	
512 496
 	private int offsetToX(int line, int offset) {
513
-		if (line >= lines.size())
497
+		if (line >= tokens.getLineCount())
514 498
 			return 0;
515 499
 		
516 500
 		int tokensOffset = 0;
517 501
 		int x = bounds.x + lineBarWidth + 10;
518
-		Line lineData = lines.get(line);
519
-		for (ZSToken token : lineData.tokens) {
502
+		TokenLine lineData = tokens.getLine(line);
503
+		for (ZSToken token : lineData.getTokens()) {
520 504
 			String content = getDisplayContent(token);
521 505
 			if (tokensOffset + token.content.length() >= offset) {
522 506
 				if (token.type == ZSTokenType.T_WHITESPACE_TAB)
@@ -535,190 +519,28 @@ public class SourceEditor implements DComponent {
535 519
 		return token.type == ZSTokenType.T_WHITESPACE_TAB ? tab : token.content;
536 520
 	}
537 521
 	
538
-	private void parse() throws IOException {
539
-		lines.clear();
540
-		lines.add(new Line());
541
-		
542
-		TokenParser<ZSToken, ZSTokenType> parser = ZSTokenParser.createRaw(sourceFile.getName(), new ReaderCharReader(sourceFile.read()), tab.length());
543
-		insertTokens(0, 0, parser);
544
-	}
545
-	
546 522
 	private void onLinesUpdated() {
547
-		dimensionPreferences.setValue(new DDimensionPreferences(0, fullLineHeight * lines.size()));
523
+		dimensionPreferences.setValue(new DDimensionPreferences(0, fullLineHeight * tokens.getLineCount()));
548 524
 		
549 525
 		if (bounds != null)
550 526
 			context.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
551 527
 	}
552 528
 	
553
-	public class Line {
554
-		public final List<ZSToken> tokens = new ArrayList<>();
555
-		private int length;
556
-		
557
-		private void calculateLength() {
558
-			length = 0;
559
-			for (ZSToken token : tokens)
560
-				length += token.content.length();
561
-		}
562
-		
563
-		public void merge(int lineIndex, Line other) {
564
-			if (tokens.isEmpty()) {
565
-				tokens.addAll(other.tokens);
566
-				length = other.length;
567
-				return;
568
-			}
569
-			
570
-			int fromToken = tokens.size() - 1;
571
-			int toToken = other.tokens.size() > 0 ? fromToken + 2 : fromToken + 1;
572
-			tokens.addAll(other.tokens);
573
-			length += other.length;
574
-			reparse(lineIndex, fromToken, lineIndex, toToken);
575
-		}
576
-		
577
-		public Line split(int lineIndex, int offset) {
578
-			if (offset < 0)
579
-				throw new IllegalArgumentException("offset must be >= 0");
580
-			
581
-			int tokenOffset = 0;
582
-			for (int i = 0; i < tokens.size(); i++) {
583
-				ZSToken token = tokens.get(i);
584
-				if (tokenOffset + token.content.length() > offset) {
585
-					ZSToken broken = token;
586
-					Line newLine = new Line();
587
-					if (offset - tokenOffset != token.content.length() && offset - tokenOffset != 0) {
588
-						ZSToken.Pair split = token.split(offset - tokenOffset);
589
-						broken = split.first;
590
-						newLine.tokens.add(split.second);
591
-					}
592
-					
593
-					tokens.set(i, broken);
594
-					for (int j = i + 1; j < tokens.size(); j++)
595
-						newLine.tokens.add(tokens.get(j));
596
-					for (int j = tokens.size() - 1; j > i; j--)
597
-						tokens.remove(j);
598
-					
599
-					lines.add(lineIndex + 1, newLine);
600
-					reparse(lineIndex, i, lineIndex + 1, 1);
601
-					return newLine;
602
-				}
603
-				tokenOffset += token.content.length();
604
-			}
605
-			
606
-			Line result = new Line();
607
-			lines.add(result);
608
-			return result;
609
-		}
610
-		
611
-		public void delete(int lineIndex, int offset) {
612
-			int tokenOffset = 0;
613
-			for (int i = 0; i < tokens.size(); i++) {
614
-				ZSToken token = tokens.get(i);
615
-				if (tokenOffset + token.content.length() > offset) {
616
-					if (token.content.length() == 1) {
617
-						tokens.remove(i);
618
-					} else {
619
-						token = token.delete(offset - tokenOffset, 1);
620
-						tokens.set(i, token);
621
-					}
622
-					length--;
623
-					
624
-					reparse(lineIndex, i, lineIndex, i + 1);
625
-					return;
626
-				}
627
-				tokenOffset += token.content.length();
628
-			}
629
-		}
630
-		
631
-		public void insert(int lineIndex, int offset, String value) {
632
-			int tokenOffset = 0;
633
-			if (tokens.isEmpty()) {
634
-				tokens.add(new ZSToken(ZSTokenType.INVALID, value));
635
-				reparse(lineIndex, 0, lineIndex, 1);
636
-				return;
637
-			}
638
-			
639
-			for (int i = 0; i < tokens.size(); i++) {
640
-				ZSToken token = tokens.get(i);
641
-				if (tokenOffset + token.content.length() > offset) {
642
-					token = token.insert(offset - tokenOffset, value);
643
-					tokens.set(i, token);
644
-					length += value.length();
645
-					reparse(lineIndex, i, lineIndex, i + 1);
646
-					return;
647
-				}
648
-				tokenOffset += token.content.length();
649
-			}
650
-			
651
-			ZSToken token = tokens.get(tokens.size() - 1);
652
-			token = new ZSToken(token.type, token.content + value);
653
-			tokens.set(tokens.size() - 1, token);
654
-			length += value.length();
655
-			reparse(lineIndex, tokens.size() - 1, lineIndex, tokens.size());
656
-		}
657
-	}
658
-	
659
-	private void reparse(int fromLine, int fromToken, int toLine, int toToken) {
660
-		TokenReparser reparser = new TokenReparser(sourceFile.getName(), lines, fromLine, fromToken, toLine, toToken, tab.length());
661
-		List<ZSToken> tokens = reparser.reparse();
662
-		replaceTokens(fromLine, fromToken, reparser.getLine(), reparser.getToken(), tokens);
663
-	}
664
-	
665
-	private void replaceTokens(int fromLine, int fromToken, int toLine, int toToken, List<ZSToken> tokens) {
666
-		removeTokens(fromLine, fromToken, toLine, toToken);
667
-		insertTokens(fromLine, fromToken, tokens.iterator());
668
-	}
669
-	
670
-	private void removeTokens(int fromLine, int fromToken, int toLine, int toToken) {
671
-		if (toLine > fromLine) {
672
-			Line fromLineObject = lines.get(fromLine);
673
-			for (int i = fromLineObject.tokens.size() - 1; i >= fromToken; i--)
674
-				fromLineObject.tokens.remove(i);
675
-			
676
-			Line toLineObject = lines.get(fromLine);
677
-			for (int i = toToken - 1; i >= 0; i--)
678
-				toLineObject.tokens.remove(i);
679
-			
680
-			fromLineObject.merge(fromLine, lines.remove(toLine));
681
-			for (int i = toLine - 1; i > fromLine; i--)
682
-				lines.remove(i);
683
-			
529
+	private class TokenListener implements TokenModel.Listener {
530
+
531
+		@Override
532
+		public void onLineInserted(int index) {
684 533
 			onLinesUpdated();
685
-		} else {
686
-			Line line = lines.get(fromLine);
687
-			for (int i = toToken - 1; i >= fromToken; i--)
688
-				line.tokens.remove(i);
689 534
 		}
690
-	}
691
-	
692
-	private void insertTokens(int line, int tokenIndex, Iterator<ZSToken> tokens) {
693
-		boolean linesUpdated = false;
694
-		Line currentLine = lines.get(line);
695
-		while (tokens.hasNext()) {
696
-			ZSToken token = tokens.next();
697
-			if (token.type == ZSTokenType.T_WHITESPACE_NEWLINE) {
698
-				currentLine.calculateLength();
699
-				
700
-				Line newLine = new Line();
701
-				if (tokenIndex < currentLine.tokens.size()) {
702
-					for (int i = tokenIndex; i < currentLine.tokens.size(); i++) {
703
-						newLine.tokens.add(currentLine.tokens.remove(tokenIndex));
704
-					}
705
-				}
706
-				currentLine = newLine;
707
-				tokenIndex = 0;
708
-				lines.add(++line, currentLine);
709
-				linesUpdated = true;
710
-			} else if (token.type != ZSTokenType.T_WHITESPACE_CARRIAGE_RETURN) {
711
-				currentLine.tokens.add(tokenIndex++, token);
712
-			}
535
+
536
+		@Override
537
+		public void onLineChanged(int index) {
538
+			repaintLine(index);
713 539
 		}
714
-		
715
-		currentLine.calculateLength();
716
-		
717
-		if (linesUpdated) {
540
+
541
+		@Override
542
+		public void onLineDeleted(int index) {
718 543
 			onLinesUpdated();
719
-		} else {
720
-			// Could be further optimized to only repaint the modified characters
721
-			repaintLine(line);
722 544
 		}
723 545
 	}
724 546
 	
@@ -740,6 +562,9 @@ public class SourceEditor implements DComponent {
740 562
 		
741 563
 		public static TokenClass get(ZSTokenType type) {
742 564
 			switch (type) {
565
+				case T_COMMENT_SCRIPT:
566
+				case T_COMMENT_SINGLELINE:
567
+				case T_COMMENT_MULTILINE:
743 568
 				case T_WHITESPACE_CARRIAGE_RETURN:
744 569
 				case T_WHITESPACE_NEWLINE:
745 570
 				case T_WHITESPACE_SPACE:

+ 89
- 0
IDE/src/main/java/org/openzen/zenscript/ide/ui/view/editor/TokenLine.java Bestand weergeven

@@ -0,0 +1,89 @@
1
+/*
2
+ * To change this license header, choose License Headers in Project Properties.
3
+ * To change this template file, choose Tools | Templates
4
+ * and open the template in the editor.
5
+ */
6
+package org.openzen.zenscript.ide.ui.view.editor;
7
+
8
+import java.util.ArrayList;
9
+import java.util.Collections;
10
+import java.util.List;
11
+import org.openzen.zenscript.lexer.ZSToken;
12
+import org.openzen.zenscript.lexer.ZSTokenType;
13
+
14
+/**
15
+ *
16
+ * @author Hoofdgebruiker
17
+ */
18
+public final class TokenLine {
19
+	private final List<ZSToken> tokens = new ArrayList<>();
20
+	private int length = 0;
21
+	
22
+	public List<ZSToken> getTokens() {
23
+		return Collections.unmodifiableList(tokens);
24
+	}
25
+	
26
+	public boolean isEmpty() {
27
+		return tokens.isEmpty();
28
+	}
29
+	
30
+	public int getTokenCount() {
31
+		return tokens.size();
32
+	}
33
+
34
+	public int length() {
35
+		return length;
36
+	}
37
+	
38
+	public ZSToken getToken(int index) {
39
+		return tokens.get(index);
40
+	}
41
+	
42
+	public ZSToken getLastToken() {
43
+		return tokens.get(tokens.size() - 1);
44
+	}
45
+	
46
+	public String getIndent() {
47
+		StringBuilder indent = new StringBuilder();
48
+		for (int i = 0; i < tokens.size(); i++) {
49
+			ZSToken token = tokens.get(i);
50
+			if (token.type != ZSTokenType.T_WHITESPACE_SPACE && token.type != ZSTokenType.T_WHITESPACE_TAB)
51
+				break;
52
+			
53
+			indent.append(token.content);
54
+		}
55
+		return indent.toString();
56
+	}
57
+
58
+	public void add(ZSToken token) {
59
+		tokens.add(token);
60
+		length += token.content.length();
61
+	}
62
+
63
+	public void addAll(Iterable<ZSToken> tokens) {
64
+		for (ZSToken token : tokens) {
65
+			add(token);
66
+		}
67
+	}
68
+
69
+	public void insert(int index, ZSToken token) {
70
+		tokens.add(index, token);
71
+		length += token.content.length();
72
+	}
73
+
74
+	public void replace(int index, ZSToken token) {
75
+		ZSToken old = tokens.set(index, token);
76
+		length += token.content.length() - old.content.length();
77
+	}
78
+
79
+	public ZSToken remove(int index) {
80
+		ZSToken result = tokens.remove(index);
81
+		length -= result.content.length();
82
+		return result;
83
+	}
84
+	
85
+	public void removeRange(int from, int to) {
86
+		for (int i = to - 1; i >= from; i--)
87
+			remove(i);
88
+	}
89
+}

+ 241
- 0
IDE/src/main/java/org/openzen/zenscript/ide/ui/view/editor/TokenModel.java Bestand weergeven

@@ -0,0 +1,241 @@
1
+/*
2
+ * To change this license header, choose License Headers in Project Properties.
3
+ * To change this template file, choose Tools | Templates
4
+ * and open the template in the editor.
5
+ */
6
+package org.openzen.zenscript.ide.ui.view.editor;
7
+
8
+import java.util.ArrayList;
9
+import java.util.Collections;
10
+import java.util.HashSet;
11
+import java.util.Iterator;
12
+import java.util.List;
13
+import java.util.Set;
14
+import org.openzen.drawablegui.listeners.ListenerHandle;
15
+import org.openzen.drawablegui.listeners.ListenerList;
16
+import org.openzen.zenscript.lexer.ZSToken;
17
+import org.openzen.zenscript.lexer.ZSTokenType;
18
+
19
+/**
20
+ *
21
+ * @author Hoofdgebruiker
22
+ */
23
+public class TokenModel {
24
+	private final ListenerList<Listener> listeners = new ListenerList<>();
25
+	
26
+	private final String filename;
27
+	private final int spacesPerTab;
28
+	private final List<TokenLine> lines = new ArrayList<>();
29
+	
30
+	public TokenModel(String filename, int spacesPerTab) {
31
+		this.filename = filename;
32
+		this.spacesPerTab = spacesPerTab;
33
+	}
34
+	
35
+	public ListenerHandle<Listener> addListener(Listener listener) {
36
+		return listeners.add(listener);
37
+	}
38
+	
39
+	public int getLineCount() {
40
+		return lines.size();
41
+	}
42
+	
43
+	public TokenLine getLine(int line) {
44
+		return lines.get(line);
45
+	}
46
+	
47
+	public List<TokenLine> getLines() {
48
+		return Collections.unmodifiableList(lines);
49
+	}
50
+	
51
+	public int getLineLength(int line) {
52
+		return lines.get(line).length();
53
+	}
54
+	
55
+	public void set(Iterator<ZSToken> tokens) {
56
+		lines.clear();
57
+		lines.add(new TokenLine());
58
+		
59
+		insertTokens(0, 0, tokens);
60
+	}
61
+	
62
+	public void deleteNewline(int lineIndex) {
63
+		TokenLine line = getLine(lineIndex);
64
+		merge(line, lineIndex, getLine(lineIndex + 1));
65
+		lines.remove(lineIndex + 1);
66
+		
67
+		listeners.accept(listener -> listener.onLineDeleted(lineIndex + 1));
68
+	}
69
+	
70
+	public void deleteCharacter(int lineIndex, int offset) {
71
+		TokenLine line = getLine(lineIndex);
72
+		int tokenOffset = 0;
73
+		for (int i = 0; i < line.getTokenCount(); i++) {
74
+			ZSToken token = line.getToken(i);
75
+			if (tokenOffset + token.content.length() > offset) {
76
+				if (token.content.length() == 1) {
77
+					line.remove(i);
78
+					i--; // make sure previous token is reparsed too
79
+				} else {
80
+					token = token.delete(offset - tokenOffset, 1);
81
+					line.replace(i, token);
82
+				}
83
+
84
+				reparse(lineIndex, i, lineIndex, i + 1);
85
+				return;
86
+			}
87
+			tokenOffset += token.content.length();
88
+		}
89
+	}
90
+	
91
+	public void insert(int lineIndex, int offset, String value) {
92
+		TokenLine line = lines.get(lineIndex);
93
+		int tokenOffset = 0;
94
+		if (line.isEmpty()) {
95
+			line.add(new ZSToken(ZSTokenType.INVALID, value));
96
+			reparse(lineIndex, 0, lineIndex, 1);
97
+			return;
98
+		}
99
+
100
+		for (int i = 0; i < line.getTokenCount(); i++) {
101
+			ZSToken token = line.getToken(i);
102
+			if (tokenOffset + token.content.length() > offset) {
103
+				token = token.insert(offset - tokenOffset, value);
104
+				line.replace(i, token);
105
+				reparse(lineIndex, i, lineIndex, i + 1);
106
+				return;
107
+			}
108
+			tokenOffset += token.content.length();
109
+		}
110
+
111
+		ZSToken token = line.getLastToken();
112
+		token = new ZSToken(token.type, token.content + value);
113
+		line.replace(line.getTokenCount() - 1, token);
114
+		reparse(lineIndex, line.getTokenCount() - 1, lineIndex, line.getTokenCount());
115
+	}
116
+	
117
+	private void reparse(int fromLine, int fromToken, int toLine, int toToken) {
118
+		TokenReparser reparser = new TokenReparser(filename, lines, fromLine, fromToken, toLine, toToken, spacesPerTab);
119
+		List<ZSToken> tokens = reparser.reparse();
120
+		replaceTokens(fromLine, fromToken, reparser.getLine(), reparser.getToken(), tokens);
121
+	}
122
+	
123
+	private void replaceTokens(int fromLine, int fromToken, int toLine, int toToken, List<ZSToken> tokens) {
124
+		removeTokens(fromLine, fromToken, toLine, toToken);
125
+		insertTokens(fromLine, fromToken, tokens.iterator());
126
+	}
127
+	
128
+	private void removeTokens(int fromLine, int fromToken, int toLine, int toToken) {
129
+		if (toLine > fromLine) {
130
+			TokenLine fromLineObject = lines.get(fromLine);
131
+			fromLineObject.removeRange(fromToken, fromLineObject.getTokenCount());
132
+			
133
+			listeners.accept(listener -> listener.onLineChanged(fromLine));
134
+			
135
+			TokenLine toLineObject = lines.get(toLine);
136
+			for (int i = toToken - 1; i >= 0; i--)
137
+				toLineObject.remove(i);
138
+			
139
+			listeners.accept(listener -> listener.onLineChanged(toLine));
140
+			
141
+			merge(lines.get(fromLine), fromLine, lines.remove(toLine));
142
+			for (int i = toLine - 1; i > fromLine; i--) {
143
+				lines.remove(i);
144
+				
145
+				int ix = i;
146
+				listeners.accept(listener -> listener.onLineDeleted(ix));
147
+			}
148
+		} else {
149
+			TokenLine line = lines.get(fromLine);
150
+			for (int i = toToken - 1; i >= fromToken; i--)
151
+				line.remove(i);
152
+			
153
+			listeners.accept(listener -> listener.onLineChanged(fromLine));
154
+		}
155
+	}
156
+	
157
+	private void insertTokens(int line, int tokenIndex, Iterator<ZSToken> tokens) {
158
+		TokenLine currentLine = lines.get(line);
159
+		Set<Integer> insertedLines = new HashSet<>();
160
+		Set<Integer> modifiedLines = new HashSet<>();
161
+		while (tokens.hasNext()) {
162
+			ZSToken token = tokens.next();
163
+			if (token.type.multiline && token.content.indexOf('\n') >= 0) {
164
+				TokenLine newLine = new TokenLine();
165
+				if (tokenIndex < currentLine.getTokenCount()) {
166
+					for (int i = currentLine.getTokenCount() - 1; i >= tokenIndex; i--) {
167
+						newLine.insert(0, currentLine.remove(i));
168
+					}
169
+				}
170
+				
171
+				tokenIndex = 0;
172
+				if (!token.content.equals("\n")) {
173
+					String[] parts = token.content.split("\r?\n");
174
+					if (!parts[0].isEmpty())
175
+						currentLine.add(new ZSToken(token.type, parts[0]));
176
+					if (!parts[parts.length - 1].isEmpty()) {
177
+						newLine.insert(0, new ZSToken(token.type, parts[parts.length - 1]));
178
+						tokenIndex++;
179
+					}
180
+					
181
+					for (int i = 1; i < parts.length - 1; i++) {
182
+						TokenLine intermediate = new TokenLine();
183
+						if (!parts[i].isEmpty())
184
+							intermediate.add(new ZSToken(token.type, parts[i]));
185
+						lines.add(++line, intermediate);
186
+					}
187
+				}
188
+				
189
+				currentLine = newLine;
190
+				lines.add(++line, currentLine);
191
+				insertedLines.add(line);
192
+			} else if (token.type != ZSTokenType.T_WHITESPACE_CARRIAGE_RETURN) {
193
+				currentLine.insert(tokenIndex++, token);
194
+				modifiedLines.add(line);
195
+			}
196
+		}
197
+		
198
+		listeners.accept(listener -> {
199
+			for (Integer inserted : insertedLines)
200
+				listener.onLineInserted(inserted);
201
+			for (Integer modified : modifiedLines)
202
+				if (!insertedLines.contains(modified))
203
+					listener.onLineChanged(modified);
204
+		});
205
+	}
206
+	
207
+	@Override
208
+	public String toString() {
209
+		StringBuilder result = new StringBuilder();
210
+		for (int i = 0; i < lines.size(); i++) {
211
+			if (i > 0)
212
+				result.append("\n");
213
+			
214
+			TokenLine line = lines.get(i);
215
+			for (ZSToken token : line.getTokens()) {
216
+				result.append(token.content);
217
+			}
218
+		}
219
+		return result.toString();
220
+	}
221
+		
222
+	private void merge(TokenLine line, int lineIndex, TokenLine other) {
223
+		if (line.isEmpty()) {
224
+			line.addAll(other.getTokens());
225
+			return;
226
+		}
227
+
228
+		int fromToken = line.getTokenCount() - 1;
229
+		int toToken = !other.isEmpty() ? fromToken + 2 : fromToken + 1;
230
+		line.addAll(other.getTokens());
231
+		reparse(lineIndex, fromToken, lineIndex, toToken);
232
+	}
233
+	
234
+	public interface Listener {
235
+		void onLineInserted(int index);
236
+		
237
+		void onLineChanged(int index);
238
+		
239
+		void onLineDeleted(int index);
240
+	}
241
+}

+ 7
- 7
IDE/src/main/java/org/openzen/zenscript/ide/ui/view/editor/TokenReparser.java Bestand weergeven

@@ -19,7 +19,7 @@ import org.openzen.zenscript.lexer.ZSTokenType;
19 19
  * @author Hoofdgebruiker
20 20
  */
21 21
 public class TokenReparser {
22
-	private final List<SourceEditor.Line> lines;
22
+	private final List<TokenLine> lines;
23 23
 	private final String filename;
24 24
 	private final int toLine;
25 25
 	private final int toToken;
@@ -28,7 +28,7 @@ public class TokenReparser {
28 28
 	private int lineIndex;
29 29
 	private int token;
30 30
 	
31
-	public TokenReparser(String filename, List<SourceEditor.Line> lines, int fromLine, int fromToken, int toLine, int toToken, int spacesPerTab) {
31
+	public TokenReparser(String filename, List<TokenLine> lines, int fromLine, int fromToken, int toLine, int toToken, int spacesPerTab) {
32 32
 		if (fromToken < 0 || toToken < 0)
33 33
 			throw new IllegalArgumentException("fromToken or toToken cannot be < 0");
34 34
 		
@@ -52,19 +52,19 @@ public class TokenReparser {
52 52
 	}
53 53
 	
54 54
 	private String get() {
55
-		SourceEditor.Line line = lines.get(lineIndex);
56
-		if (token == line.tokens.size())
55
+		TokenLine line = lines.get(lineIndex);
56
+		if (token == line.getTokenCount())
57 57
 			return "\n"; // special "newline" token for transitions to next line
58 58
 		
59
-		return line.tokens.get(token).content;
59
+		return line.getToken(token).content;
60 60
 	}
61 61
 	
62 62
 	private boolean advance() {
63
-		if (lineIndex == lines.size() - 1 && token == lines.get(lineIndex).tokens.size())
63
+		if (lineIndex == lines.size() - 1 && token == lines.get(lineIndex).getTokenCount())
64 64
 			return false;
65 65
 		
66 66
 		token++;
67
-		if (token > lines.get(lineIndex).tokens.size()) {
67
+		if (token > lines.get(lineIndex).getTokenCount()) {
68 68
 			lineIndex++;
69 69
 			token = 0;
70 70
 		}

+ 2
- 1
Parser/src/main/java/org/openzen/zenscript/lexer/ZSToken.java Bestand weergeven

@@ -43,6 +43,7 @@ public class ZSToken implements Token<ZSTokenType> {
43 43
 		return content;
44 44
 	}
45 45
 	
46
+	@Override
46 47
 	public String toString() {
47 48
 		return type + ":" + content;
48 49
 	}
@@ -50,7 +51,7 @@ public class ZSToken implements Token<ZSTokenType> {
50 51
 	public ZSToken delete(int offset, int characters) {
51 52
 		return new ZSToken(
52 53
 				ZSTokenType.INVALID,
53
-				content.substring(0, offset) + content.substring(offset));
54
+				content.substring(0, offset) + content.substring(offset + 1));
54 55
 	}
55 56
 	
56 57
 	public Pair deleteAndSplit(int offset, int characters) {

+ 25
- 2
Parser/src/main/java/org/openzen/zenscript/lexer/ZSTokenType.java Bestand weergeven

@@ -12,10 +12,10 @@ package org.openzen.zenscript.lexer;
12 12
 public enum ZSTokenType implements TokenType {
13 13
 	T_COMMENT_SCRIPT("#[^\n]*[\n\\e]", true),
14 14
 	T_COMMENT_SINGLELINE("//[^\n]*[\n\\e]", true),
15
-	T_COMMENT_MULTILINE("/\\*([^\\*]|(\\*+([^\\*/])))*\\*+/", true),
15
+	T_COMMENT_MULTILINE("/\\*([^\\*]|(\\*+([^\\*/])))*\\*+/", true, true),
16 16
 	T_WHITESPACE_SPACE(true, " ", " "),
17 17
 	T_WHITESPACE_TAB(true, "\t", "\t"),
18
-	T_WHITESPACE_NEWLINE(true, "\n", "\n"),
18
+	T_WHITESPACE_NEWLINE(true, "\n", "\n", true),
19 19
 	T_WHITESPACE_CARRIAGE_RETURN(true, "\r", "\r"),
20 20
 	T_IDENTIFIER("[a-zA-Z_][a-zA-Z_0-9]*"),
21 21
 	T_FLOAT("\\-?(0|[1-9][0-9]*)\\.[0-9]+([eE][\\+\\-]?[0-9]+)?"),
@@ -161,12 +161,14 @@ public enum ZSTokenType implements TokenType {
161 161
 	
162 162
 	public final ZSToken flyweight;
163 163
 	public final boolean isKeyword;
164
+	public final boolean multiline;
164 165
 	
165 166
 	private ZSTokenType() {
166 167
 		this.regexp = null;
167 168
 		this.whitespace = false;
168 169
 		this.isKeyword = false;
169 170
 		this.flyweight = null;
171
+		this.multiline = false;
170 172
 	}
171 173
 	
172 174
 	private ZSTokenType(String regexp) {
@@ -174,6 +176,7 @@ public enum ZSTokenType implements TokenType {
174 176
 		this.whitespace = false;
175 177
 		this.isKeyword = false;
176 178
 		this.flyweight = null;
179
+		this.multiline = false;
177 180
 	}
178 181
 	
179 182
 	private ZSTokenType(String regexp, boolean whitespace) {
@@ -181,6 +184,15 @@ public enum ZSTokenType implements TokenType {
181 184
 		this.whitespace = whitespace;
182 185
 		this.isKeyword = false;
183 186
 		this.flyweight = null;
187
+		this.multiline = false;
188
+	}
189
+	
190
+	private ZSTokenType(String regexp, boolean whitespace, boolean multiline) {
191
+		this.regexp = regexp;
192
+		this.whitespace = whitespace;
193
+		this.isKeyword = false;
194
+		this.flyweight = null;
195
+		this.multiline = multiline;
184 196
 	}
185 197
 	
186 198
 	private ZSTokenType(String regexp, String content) {
@@ -188,6 +200,7 @@ public enum ZSTokenType implements TokenType {
188 200
 		this.whitespace = false;
189 201
 		this.isKeyword = false;
190 202
 		this.flyweight = new ZSToken(this, content, content);
203
+		this.multiline = false;
191 204
 	}
192 205
 	
193 206
 	private ZSTokenType(boolean isWhitespace, String regexp, String content) {
@@ -195,6 +208,15 @@ public enum ZSTokenType implements TokenType {
195 208
 		this.whitespace = isWhitespace;
196 209
 		this.isKeyword = false;
197 210
 		this.flyweight = new ZSToken(this, content, content);
211
+		this.multiline = false;
212
+	}
213
+	
214
+	private ZSTokenType(boolean isWhitespace, String regexp, String content, boolean multiline) {
215
+		this.regexp = regexp;
216
+		this.whitespace = isWhitespace;
217
+		this.isKeyword = false;
218
+		this.flyweight = new ZSToken(this, content, content);
219
+		this.multiline = multiline;
198 220
 	}
199 221
 	
200 222
 	private ZSTokenType(boolean isKeyword, String content) {
@@ -202,6 +224,7 @@ public enum ZSTokenType implements TokenType {
202 224
 		this.whitespace = false;
203 225
 		this.isKeyword = isKeyword;
204 226
 		this.flyweight = new ZSToken(this, content, content);
227
+		this.multiline = false;
205 228
 	}
206 229
 
207 230
 	@Override

Laden…
Annuleren
Opslaan