1 21 22 package org.armedbear.j; 23 24 import gnu.regexp.RE; 25 import gnu.regexp.REException; 26 import gnu.regexp.REMatch; 27 import java.awt.event.KeyEvent ; 28 import java.io.BufferedReader ; 29 import java.io.FileInputStream ; 30 import java.io.FileNotFoundException ; 31 import java.io.IOException ; 32 import java.io.InputStream ; 33 import java.io.InputStreamReader ; 34 import java.util.List ; 35 import javax.swing.undo.CompoundEdit ; 36 37 public final class HtmlMode extends AbstractMode implements Constants, Mode 38 { 39 private static final Mode mode = new HtmlMode(); 40 private static List elements; 41 private static RE tagNameRE; 42 private static RE attributeNameRE; 43 private static RE quotedValueRE; 44 private static RE unquotedValueRE; 45 46 private HtmlMode() 47 { 48 super(HTML_MODE, HTML_MODE_NAME); 49 keywords = new Keywords(JavaScriptMode.getMode()); 51 } 52 53 public static final Mode getMode() 54 { 55 return mode; 56 } 57 58 public Formatter getFormatter(Buffer buffer) 59 { 60 return new HtmlFormatter(buffer); 61 } 62 63 protected void setKeyMapDefaults(KeyMap km) 64 { 65 km.mapKey(KeyEvent.VK_TAB, 0, "tab"); 66 km.mapKey(KeyEvent.VK_TAB, CTRL_MASK, "insertTab"); 67 km.mapKey(KeyEvent.VK_ENTER, 0, "newlineAndIndent"); 68 km.mapKey(KeyEvent.VK_ENTER, CTRL_MASK, "newline"); 69 km.mapKey(KeyEvent.VK_M, CTRL_MASK, "htmlFindMatch"); 70 km.mapKey(KeyEvent.VK_E, CTRL_MASK, "htmlInsertMatchingEndTag"); 71 km.mapKey(KeyEvent.VK_B, CTRL_MASK, "htmlBold"); 72 km.mapKey('=', "htmlElectricEquals"); 73 km.mapKey('>', "electricCloseAngleBracket"); 74 km.mapKey(KeyEvent.VK_V, CTRL_MASK | ALT_MASK, "viewPage"); 75 km.mapKey(KeyEvent.VK_I, ALT_MASK, "cycleIndentSize"); 76 77 km.mapKey(KeyEvent.VK_COMMA, CTRL_MASK | SHIFT_MASK, "htmlInsertTag"); 79 km.mapKey(KeyEvent.VK_PERIOD, CTRL_MASK | SHIFT_MASK, "htmlEndTag"); 80 81 km.mapKey(0x7c, CTRL_MASK | SHIFT_MASK, "htmlInsertTag"); 83 km.mapKey(0x7e, CTRL_MASK | SHIFT_MASK, "htmlEndTag"); 84 } 85 86 public boolean canIndent() 87 { 88 return true; 89 } 90 91 public boolean canIndentPaste() 92 { 93 return false; 94 } 95 96 public int getCorrectIndentation(Line line, Buffer buffer) 97 { 98 if (line.flags() == STATE_SCRIPT) 99 return JavaScriptMode.getMode().getCorrectIndentation(line, buffer); 100 if (line.flags() == STATE_HTML_COMMENT) 102 return buffer.getIndentation(line); if (line.trim().startsWith("<!--")) 104 return buffer.getIndentation(line); Line model = getModel(line); 106 if (model == null) 107 return 0; 108 int indent = buffer.getIndentation(model); 109 if (line.trim().startsWith("</")) { 110 Position pos = findMatchingStartTag(line); 111 if (pos != null) 112 return buffer.getIndentation(pos.getLine()); 113 indent -= buffer.getIndentSize(); 114 if (indent < 0) 115 indent = 0; 116 return indent; 117 } 118 final String trim = model.trim(); 119 if (trim.startsWith("<") && !trim.startsWith("</")) { 120 if (trim.startsWith("<!")) return indent; 122 String name = Utilities.getTagName(trim).toLowerCase(); 124 if (name.equals("html") || name.equals("head") || name.equals("body") || name.equals("form")) 125 return indent; 126 boolean wantsEndTag = wantsEndTag(name); 127 if (wantsEndTag) { 128 String startTag = "<" + name; 129 String endTag = "</" + name; 130 int count = 1; 131 int limit = trim.length(); 132 for (int i = startTag.length(); i < limit; i++) { 133 if (trim.charAt(i) == '<') { 134 if (lookingAtIgnoreCase(trim, i, endTag)) 135 --count; 136 else if (lookingAtIgnoreCase(trim, i, startTag)) 137 ++count; 138 } 139 } 140 if (count > 0) 141 indent += buffer.getIndentSize(); 142 } 143 } 144 return indent; 145 } 146 147 private Position findMatchingStartTag(Line line) 149 { 150 String s = line.trim(); 151 if (!s.startsWith("</")) 152 return null; 153 FastStringBuffer sb = new FastStringBuffer(); 154 for (int i = 2; i < s.length(); i++) { 155 char c = s.charAt(i); 156 if (c <= ' ') 157 break; 158 if (c == '>') 159 break; 160 sb.append(c); 161 } 162 String name = sb.toString(); 163 String toBeMatched = "</" + name + ">"; 164 String match = "<" + name; 165 int count = 1; 166 boolean foundIt = false; 167 Position pos = new Position(line, 0); 168 final String commentStart = "<!--"; 169 final String commentEnd = "-->"; 170 while (!pos.atStart()) { 172 pos.prev(); 173 if (pos.lookingAt(commentEnd)) { 174 do { 175 pos.prev(); 176 } while (!pos.atStart() && !pos.lookingAt(commentStart)); 177 } else if (pos.lookingAtIgnoreCase(toBeMatched)) { 178 ++count; 179 } else if (pos.lookingAtIgnoreCase(match)) { 180 if (pos.lookingAtIgnoreCase(match + ">")) 181 --count; 182 else if (pos.lookingAtIgnoreCase(match + " ")) 183 --count; 184 else if (pos.lookingAtIgnoreCase(match + "\t")) 185 --count; 186 if (count == 0) { 187 foundIt = true; 188 break; 189 } 190 } 191 } 192 if (foundIt) 193 return pos; 194 return null; 196 } 197 198 private static final boolean lookingAtIgnoreCase(String s, int i, String pattern) 199 { 200 return s.regionMatches(true, i, pattern, 0, pattern.length()); 201 } 202 203 private static Line getModel(Line line) 204 { 205 Line model = line; 206 while ((model = model.previous()) != null) { 207 if (model.flags() == STATE_HTML_COMMENT) 208 continue; 209 if (model.trim().startsWith("<!--")) 210 continue; 211 if (model.isBlank()) 212 continue; 213 break; 214 } 215 return model; 216 } 217 218 public char fixCase(Editor editor, char c) 219 { 220 if (!editor.getBuffer().getBooleanProperty(Property.FIX_CASE)) 221 return c; 222 if (!initRegExps()) 223 return c; 224 Position pos = findStartOfTag(editor.getDot()); 225 if (pos != null) { 226 int index = pos.getOffset(); 227 String text = pos.getLine().getText(); 228 REMatch match = tagNameRE.getMatch(text, index); 229 if (match == null) 230 return c; 231 if (match.getEndIndex() >= editor.getDotOffset()) { 232 if (editor.getBuffer().getBooleanProperty(Property.UPPER_CASE_TAG_NAMES)) 234 return Character.toUpperCase(c); 235 else 236 return Character.toLowerCase(c); 237 } 238 while (true) { 239 index = match.getEndIndex(); 240 match = attributeNameRE.getMatch(text, index); 241 if (match == null) 242 return c; 243 if (match.getEndIndex() >= editor.getDotOffset()) { 244 if (editor.getBuffer().getBooleanProperty(Property.UPPER_CASE_ATTRIBUTE_NAMES)) 246 return Character.toUpperCase(c); 247 else 248 return Character.toLowerCase(c); 249 } 250 index = match.getEndIndex(); 251 match = quotedValueRE.getMatch(text, index); 252 if (match == null) { 253 match = unquotedValueRE.getMatch(text, index); 254 if (match == null) 255 return c; 256 } 257 if (match.getEndIndex() >= editor.getDotOffset()) { 258 return c; 260 } 261 } 262 } 263 return c; 264 } 265 266 private static boolean checkElectricEquals(Editor editor) 267 { 268 Position pos = findStartOfTag(editor.getDot()); 269 if (pos == null) 270 return false; 271 char c = editor.getDotChar(); 272 if (c == '>' || c == '/' || Character.isWhitespace(c)) 273 return true; 274 return false; 275 } 276 277 private static Position findStartOfTag(Position pos) 278 { 279 int offset = pos.getOffset(); 280 String text = pos.getLine().getText(); 281 while (--offset >= 0) { 282 char c = text.charAt(offset); 283 if (c == '>') 284 return null; 285 if (c == '<') 286 return new Position(pos.getLine(), offset); 287 } 288 return null; 289 } 290 291 public static List elements() 292 { 293 if (elements == null) 294 loadElementList(); 295 return elements; 296 } 297 298 private static boolean wantsEndTag(String elementName) 299 { 300 elementName = elementName.trim().toLowerCase(); 301 if (elements == null) 302 loadElementList(); 303 if (elements != null) { 304 final int limit = elements.size(); 305 for (int i = 0; i < limit; i++) { 306 HtmlElement element = (HtmlElement) elements.get(i); 307 if (element.getName().equals(elementName)) 308 return element.wantsEndTag(); 309 } 310 } 311 return true; } 313 314 private static void loadElementList() 315 { 316 elements = HtmlElement.getDefaultElements(); 317 String filename = Editor.preferences().getStringProperty(Property.HTML_MODE_TAGS); 318 if (filename != null && filename.length() > 0) { 319 try { 320 FileInputStream istream = new FileInputStream (filename); 321 loadElementsFromStream(istream); 322 } 323 catch (FileNotFoundException e) { 324 Log.error(e); 325 } 326 } 327 } 328 329 private static void loadElementsFromStream(InputStream istream) 330 { 331 Debug.assertTrue(elements != null); 332 if (istream != null) { 333 try { 334 BufferedReader in = 335 new BufferedReader (new InputStreamReader (istream)); 336 while (true) { 337 String s = in.readLine(); 338 if (s == null) 339 break; s = s.trim(); 341 if (s.trim().length() == 0) 343 continue; 344 if (s.charAt(0) == '#') 346 continue; 347 int index = s.indexOf('='); 348 if (index >= 0) { 349 String name = s.substring(0, index).trim().toLowerCase(); 351 String value = s.substring(index + 1).trim(); 352 boolean wantsEndTag = value.equals("1") || value.equals("true"); 353 boolean found = false; 354 for (int i = 0; i < elements.size(); i++) { 355 HtmlElement element = (HtmlElement) elements.get(i); 356 if (element.getName().equals(name)) { 357 element.setWantsEndTag(wantsEndTag); 358 found = true; 359 break; 360 } 361 } 362 if (!found) 363 elements.add(new HtmlElement(name, wantsEndTag)); 364 } 365 } 366 } 367 catch (IOException e) { 368 Log.error(e); 369 } 370 } 371 } 372 373 private static boolean initRegExps() 374 { 375 if (tagNameRE == null) { 376 try { 377 tagNameRE = new RE("</?[A-Za-z0-9]*"); 378 attributeNameRE = new RE("\\s+[A-Za-z0-9]*"); 379 quotedValueRE = new RE("\\s*=\\s*\"[^\"]*"); 380 unquotedValueRE = new RE("\\s*=\\s*\\S*"); 381 } 382 catch (REException e) { 383 tagNameRE = null; 384 return false; 385 } 386 } 387 return true; 388 } 389 390 public static void htmlStartTag() 391 { 392 htmlTag(Editor.currentEditor(), false); 393 } 394 395 public static void htmlEndTag() 396 { 397 htmlTag(Editor.currentEditor(), true); 398 } 399 400 private static void htmlTag(Editor editor, boolean isEndTag) 401 { 402 if (!editor.checkReadOnly()) 403 return; 404 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 405 editor.insertChar('<'); 406 if (isEndTag) 407 editor.insertChar('/'); 408 editor.insertChar('>'); 409 editor.addUndo(SimpleEdit.MOVE); 410 editor.getDot().moveLeft(); 411 editor.moveCaretToDotCol(); 412 editor.endCompoundEdit(compoundEdit); 413 } 414 415 public static void htmlInsertTag() 416 { 417 final Editor editor = Editor.currentEditor(); 418 if (!editor.checkReadOnly()) 419 return; 420 InsertTagDialog d = new InsertTagDialog(editor); 421 editor.centerDialog(d); 422 d.show(); 423 _htmlInsertTag(editor, d.getInput()); 424 } 425 426 public static void htmlInsertTag(String input) 427 { 428 final Editor editor = Editor.currentEditor(); 429 if (!editor.checkReadOnly()) 430 return; 431 _htmlInsertTag(editor, input); 432 } 433 434 private static void _htmlInsertTag(Editor editor, String input) 435 { 436 if (input != null && input.length() > 0) { 437 final String tagName, extra; 438 int index = input.indexOf(' '); 439 if (index >= 0) { 440 tagName = input.substring(0, index); 441 extra = input.substring(index); 442 } else { 443 tagName = input; 444 extra = ""; 445 } 446 InsertTagDialog.insertTag(editor, tagName, extra, wantsEndTag(tagName)); 447 } 448 } 449 450 public static void htmlInsertMatchingEndTag() 451 { 452 final Editor editor = Editor.currentEditor(); 453 if (!editor.checkReadOnly()) 454 return; 455 Position pos = editor.getDotCopy(); 456 while (pos.prev()) { 457 if (pos.lookingAt("</")) 459 return; 460 461 if (pos.getChar() == '<') { 462 if (pos.next()) { 463 FastStringBuffer sb = new FastStringBuffer(); 464 char c; 465 while (!Character.isWhitespace(c = pos.getChar()) && c != '>') { 466 sb.append(c); 467 if (!pos.next()) 468 return; 469 } 470 if (sb.length() == 0) 471 return; 472 final String endTag = "</" + sb.toString() + ">"; 473 final Buffer buffer = editor.getBuffer(); 474 try { 475 buffer.lockWrite(); 476 } 477 catch (InterruptedException e) { 478 Log.error(e); 479 return; 480 } 481 try { 482 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 483 editor.fillToCaret(); 484 editor.addUndo(SimpleEdit.INSERT_STRING); 485 editor.insertStringInternal(endTag); 486 buffer.modified(); 487 editor.addUndo(SimpleEdit.MOVE); 488 editor.moveCaretToDotCol(); 489 if (buffer.getBooleanProperty(Property.AUTO_INDENT)) 490 editor.indentLine(); 491 editor.endCompoundEdit(compoundEdit); 492 } 493 finally { 494 buffer.unlockWrite(); 495 } 496 } 497 return; 498 } 499 } 500 } 501 502 public static void htmlBold() 503 { 504 final Editor editor = Editor.currentEditor(); 505 final Buffer buffer = editor.getBuffer(); 506 if (!editor.checkReadOnly()) 507 return; 508 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 509 if (editor.getMark() == null) 510 editor.fillToCaret(); 511 boolean upper = buffer.getBooleanProperty(Property.UPPER_CASE_TAG_NAMES); 512 InsertTagDialog.insertTag(editor, upper ? "B" : "b", "", true); 513 editor.endCompoundEdit(compoundEdit); 514 } 515 516 public static void htmlElectricEquals() 517 { 518 final Editor editor = Editor.currentEditor(); 519 if (!editor.checkReadOnly()) 520 return; 521 boolean ok = false; 522 if (editor.getModeId() == HTML_MODE) { 523 if (editor.getBuffer().getBooleanProperty(Property.ATTRIBUTES_REQUIRE_QUOTES)) 524 if (checkElectricEquals(editor)) 525 ok = true; 526 } 527 if (ok) { 528 CompoundEdit compoundEdit = editor.beginCompoundEdit(); 529 editor.fillToCaret(); 530 editor.addUndo(SimpleEdit.INSERT_STRING); 531 editor.insertStringInternal("=\"\""); 532 editor.addUndo(SimpleEdit.MOVE); 533 editor.getDot().moveLeft(); 534 editor.moveCaretToDotCol(); 535 editor.endCompoundEdit(compoundEdit); 536 } else 537 editor.insertNormalChar('='); 538 } 539 540 public static void htmlFindMatch() 541 { 542 final Editor editor = Editor.currentEditor(); 543 final String special = "{([})]"; 544 final String commentStart = "<!--"; 545 final String commentEnd = "-->"; 546 Position dot = editor.getDot(); 547 char c = dot.getChar(); 548 if (special.indexOf(c) >= 0) { 549 editor.findMatchingChar(); 550 return; 551 } 552 Position saved = dot.copy(); 553 while ((c = dot.getChar()) > ' ' && c != '<' && dot.getOffset() > 0) { 554 if (dot.lookingAt(commentEnd)) 555 break; 556 dot.prev(); 557 } 558 if (c <= ' ') 559 dot.next(); 560 Position start = dot.copy(); 561 FastStringBuffer sb = new FastStringBuffer(dot.getChar()); 562 dot.next(); 563 while ((c = dot.getChar()) > ' ' && c != '>') { 564 sb.append(c); 565 if (!dot.next()) { 566 editor.status("Nothing to match"); 567 dot.moveTo(saved); 568 return; 569 } 570 } 571 if (c == '>') 572 sb.append(c); 573 String toBeMatched = sb.toString(); 574 String match = null; 575 boolean searchForward = true; 576 if (toBeMatched.equals(commentStart)) 577 match = commentEnd; 578 else if (toBeMatched.equals(commentEnd)) { 579 match = commentStart; 580 searchForward = false; 581 } else if (toBeMatched.startsWith("</")) { 582 match = "<".concat(toBeMatched.substring(2)); 583 if (match.endsWith(">")) 584 match = match.substring(0, match.length()-1); 585 searchForward = false; 586 } else if (toBeMatched.startsWith("<")) { 587 if (toBeMatched.endsWith(">")) 588 toBeMatched = toBeMatched.substring(0, toBeMatched.length()-1); 589 match = "</" + toBeMatched.substring(1); 590 if (!match.endsWith(">")) 591 match += '>'; 592 } else { 593 editor.status("Nothing to match"); 594 dot.moveTo(saved); 595 return; 596 } 597 editor.setWaitCursor(); 598 int count = 1; 599 boolean succeeded = false; 600 dot.moveTo(start); 601 if (searchForward) { 602 dot.skip(toBeMatched.length()); 603 if (toBeMatched.equals(commentStart)) { 604 while (!dot.atEnd()) { 605 if (dot.lookingAt(commentEnd)) { 606 succeeded = true; 607 break; 608 } 609 dot.next(); 610 } 611 } else { 612 while (!dot.atEnd()) { 614 if (dot.lookingAt(commentStart)) { 615 dot.skip(commentStart.length()); 616 while (!dot.atEnd()) { 617 if (dot.lookingAt(commentEnd)) { 618 dot.skip(commentEnd.length()); 619 break; 620 } 621 dot.next(); 622 } 623 } else if (dot.lookingAtIgnoreCase(toBeMatched)) { 624 dot.skip(toBeMatched.length()); 625 c = dot.getChar(); 626 if (c <= ' ' || c == '>') { 627 ++count; 628 dot.next(); 629 } 630 } else if (dot.lookingAtIgnoreCase(match)) { 631 --count; 632 if (count == 0) { 633 succeeded = true; 634 break; 635 } 636 dot.skip(match.length()); 637 } else 638 dot.next(); 639 } 640 } 641 } else { 642 while (!dot.atStart()) { 644 dot.prev(); 645 if (dot.lookingAt(commentEnd)) { 646 do { 647 dot.prev(); 648 } 649 while (!dot.atStart() && !dot.lookingAt(commentStart)); 650 } else if (dot.lookingAtIgnoreCase(toBeMatched)) { 651 ++count; 652 } else if (dot.lookingAtIgnoreCase(match)) { 653 if (dot.lookingAtIgnoreCase(match + ">")) 654 --count; 655 else if (dot.lookingAtIgnoreCase(match + " ")) 656 --count; 657 else if (dot.lookingAtIgnoreCase(match + "\t")) 658 --count; 659 if (count == 0) { 660 succeeded = true; 661 break; 662 } 663 } 664 } 665 } 666 if (succeeded) { 667 Position matchPos = dot.copy(); 668 dot.moveTo(saved); 669 editor.updateDotLine(); 670 editor.addUndo(SimpleEdit.MOVE); 671 dot.moveTo(matchPos); 672 editor.updateDotLine(); 673 editor.moveCaretToDotCol(); 674 } else { 675 dot.moveTo(saved); 676 editor.status("No match"); 677 } 678 editor.setDefaultCursor(); 679 } 680 } 681 | Popular Tags |