content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.ui.components.codemirror;
  
  import org.opencms.i18n.CmsMessages;
  import org.opencms.json.JSONObject;
  import org.opencms.ui.components.Messages;
  import org.opencms.workplace.CmsWorkplace;
  
  import java.lang.reflect.Method;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Set;
  
  import com.vaadin.annotations.JavaScript;
  import com.vaadin.event.FieldEvents;
  import com.vaadin.shared.ui.JavaScriptComponentState;
  import com.vaadin.ui.AbstractJavaScriptComponent;
  import com.vaadin.ui.Button;
  import com.vaadin.ui.Component;
  import com.vaadin.ui.JavaScriptFunction;
  import com.vaadin.ui.UI;
  import com.vaadin.v7.data.Property;
  
  import elemental.json.JsonArray;
  
  /**
   * Code mirror input component.<p>
   */
  @JavaScript({"codemirror-connector.js"})
  public class CmsCodeMirror extends AbstractJavaScriptComponent
  implements Property<String>, Property.ValueChangeNotifier {
  
      /** The available editor languages. */
      public enum CodeMirrorLanguage {
          /** CSS. */
          CSS("css", new String[] {"css"}),
          /** HTML. */
          HTML("text/html", new String[] {"html", "htm", "xhtml"}),
          /** JAVA. */
          JAVA("text/x-java", new String[] {"java"}),
          /** JavaScript. */
          JAVASCRIPT("javascript", new String[] {"js"}),
          /** JSP. */
          JSP("application/x-jsp", new String[] {"jsp"}),
          /** XML. */
          XML("application/xml", new String[] {"xml", "xsd"});
  
          /** The language name. */
          private final String m_languageName;
  
          /** The supported file types. */
          private Set<String> m_supportedFileTypes;
  
          /**
           * Constructor.<p>
           *
           * @param languageName the language name
           * @param fileTypes the file types suported by this language
           */
          private CodeMirrorLanguage(String languageName, String[] fileTypes) {
  
              m_languageName = languageName;
              m_supportedFileTypes = new HashSet<String>(Arrays.asList(fileTypes));
          }
  
          /**
           * Returns the language name.<p>
           *
           * @return the language name
           */
          public String getLanguageName() {
  
              return m_languageName;
          }
  
          /**
           * Returns whether the given file ending is a supported file type.<p>
           *
           * @param fileNameSuffix the file name suffix
           *
           * @return <code>true</code> in case the file type is supported
           */
          public boolean isSupportedFileType(String fileNameSuffix) {
  
              return m_supportedFileTypes.contains(fileNameSuffix);
          }
  
          /**
           * @see java.lang.Enum#toString()
           */
          @Override
          public String toString() {
  
              return m_languageName;
          }
      }
  
      /**
       * The editor state.<p>
       */
      public static class CodeMirrorState extends JavaScriptComponentState {
  
          /** The serial version id. */
          private static final long serialVersionUID = 1L;
  
          /** The close brackets flag. */
          public boolean m_closeBrackets = true;
  
          /** The content value. */
          public String m_contentValue = "//please start to code";
  
          /** The required CSS stylesheet URIs. */
          public String[] m_cssURIs;
  
          /** The search and replace enabled flag. */
          public boolean m_enableSearchReplace;
  
          /** The undo redo enabled flag. */
          public boolean m_enableUndoRedo;
  
          /** The font size. */
          public String m_fontSize = "14px";
  
          /** The height. */
          public String m_height = "600";
  
          /** The highlighting flag. */
          public boolean m_highlighting = true;
  
          /** The editor instance id. */
          public long m_id;
  
          /** The line wrapping flag. */
          public boolean m_lineWrapping = true;
  
          /** The language mode. */
          public String m_mode = CodeMirrorLanguage.JAVASCRIPT.toString();
  
          /** The required JavaScript resource URIs. */
          public String[] m_scriptURIs;
  
          /** The short cut descriptions HTML. */
          public String m_shortcutsMessage;
  
          /** The tab visibility flag. */
          public boolean m_tabsVisible = true;
  
          /** The theme. */
          public String m_theme = CodeMirrorTheme.ECLIPSE.toString();
  
          /** The width. */
          public String m_width = "600";
  
          /** The code mirror localization. */
          public String m_messages;
      }
  
      /** The available editor themes. */
      public enum CodeMirrorTheme {
          /** Theme. */
          AMBIANCE("ambiance"),
          /** Theme. */
          BASE16_DARK("base16-dark"),
          /** Theme. */
          BASE16_LIGHT("base16-light"),
          /** Theme. */
          BLACKBOARD("blackboard"),
          /** Theme. */
          COBALT("cobalt"),
          /** Theme. */
          DAY_3024("3024-day"),
          /** Theme. */
          DEFAULT("default"),
          /** Theme. */
          ECLIPSE("eclipse"),
          /** Theme. */
          ELEGANT("elegant"),
          /** Theme. */
          ERLANG_DARK("erlang-dark"),
          /** Theme. */
          ERLANG_LIGHT("erlang-light"),
          /** Theme. */
          LESSER_DARK("lesser-dark"),
          /** Theme. */
          MBO("mbo"),
          /** Theme. */
          MDN_LIKE("mdn-like"),
          /** Theme. */
          MIDNIGHT("midnight"),
          /** Theme. */
          MONOKAI("monokai"),
          /** Theme. */
          NEAT("neat"),
          /** Theme. */
          NIGHT("night"),
          /** Theme. */
          NIGHT_3024("3024-night"),
          /** Theme. */
          PARAISO_DARK("paraiso-dark"),
          /** Theme. */
          PARAISO_LIGHT("paraiso-light"),
          /** Theme. */
          PASTEL_ON_DARK("paster-on-dark"),
          /** Theme. */
          RUBYBLUE("rubyblue"),
          /** Theme. */
          SOLARIZED_DARK("solarized dark"),
          /** Theme. */
          SOLARIZED_LIGHT("solarized light"),
          /** Theme. */
          THE_MATRIX("the-matrix"),
          /** Theme. */
          TOMORROW_NIGHT_EIGHTIES("tomorrow-night-eighties"),
          /** Theme. */
          TWILIGHT("twilight"),
          /** Theme. */
          VIBRANT_INK("vibrant-ink"),
          /** Theme. */
          XQ_DARK("xq-dark"),
          /** Theme. */
          XQ_LIGHT("xq-light");
  
          /** The theme name. */
          private final String m_themeName;
  
          /**
           * Constructor.<p>
           *
           * @param themeName the theme name
           */
          private CodeMirrorTheme(String themeName) {
  
              this.m_themeName = themeName;
          }
  
          /**
           * Returns the theme name.<p>
           *
           * @return the theme name
           */
          public String getThemeName() {
  
              return m_themeName;
          }
  
          /**
           * @see java.lang.Enum#toString()
           */
          @Override
          public String toString() {
  
              return m_themeName;
          }
  
      }
  
      /**
       * The code mirror change event.<p>
       */
      public static class ValueChangeEvent extends Component.Event implements Property.ValueChangeEvent {
  
          /** The serial version id. */
          private static final long serialVersionUID = 1L;
  
          /**
           * Constructs a new event object with the specified source field object.
           *
           * @param eventSource the field that caused the event.
           */
          public ValueChangeEvent(CmsCodeMirror eventSource) {
  
              super(eventSource);
          }
  
          /**
           * Gets the Property which triggered the event.
           *
           * @return the Source Property of the event.
           */
          @Override
          public Property getProperty() {
  
              return (Property<?>)getSource();
          }
      }
  
      /** The blur method. */
      private static final Method BLUR_METHOD;
  
      /** The required CSS stylesheet URIs. */
      private static String[] CSS_URIS;
  
      /** The HTML id prefix. */
      private static final String HTML_ID_PREFIX = "cm-addon-";
  
      /** The required JavaScript resource URIs. */
      private static String[] JAVASCRIPT_URIS;
  
      /** The code mirror component count, used as HTML ids. */
      private static long m_componentCount;
  
      /** The serial version id. */
      private static final long serialVersionUID = -4921119175861329688L;
  
      /** Them value change method. */
      private static final Method VALUE_CHANGE_METHOD;
  
      static {
          try {
              VALUE_CHANGE_METHOD = Property.ValueChangeListener.class.getDeclaredMethod(
                  "valueChange",
                  new Class[] {Property.ValueChangeEvent.class});
          } catch (final java.lang.NoSuchMethodException e) {
              // This should never happen
              throw new java.lang.RuntimeException("Internal error finding methods in ValueChangeListener");
          }
  
          try {
              BLUR_METHOD = FieldEvents.BlurListener.class.getDeclaredMethod(
                  "blur",
                  new Class[] {FieldEvents.BlurEvent.class});
          } catch (final java.lang.NoSuchMethodException e) {
              // This should never happen
              throw new java.lang.RuntimeException("Internal error finding methods in ValueChangeListener");
          }
      }
  
      /** The current content value. */
      private String m_codeValue;
  
      /** The component id. */
      private final long m_componentId;
  
      /**
       * Constructor.<p>
       */
      public CmsCodeMirror() {
  
          m_componentId = m_componentCount;
          m_componentCount++;
          m_codeValue = "";
          getState().m_id = m_componentId;
          getState().m_contentValue = m_codeValue;
          CmsMessages messages = Messages.get().getBundle(UI.getCurrent().getLocale());
          getState().m_shortcutsMessage = getShortcutMessages(messages);
          getState().m_messages = getLocalizationMessages(messages);
  
          if (CSS_URIS == null) {
              CSS_URIS = new String[] {
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/lib/codemirror.css"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/theme/eclipse.css"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/dialog/dialog.css"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/hint/show-hint.css")};
          }
          getState().m_cssURIs = CSS_URIS;
          if (JAVASCRIPT_URIS == null) {
              JAVASCRIPT_URIS = new String[] {
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/lib/codemirror.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/dialog/dialog.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/search/searchcursor.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/js/search.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/edit/closebrackets.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/edit/closetag.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/edit/matchbrackets.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/hint/show-hint.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/hint/html-hint.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/hint/javascript-hint.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/hint/xml-hint.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/fold/foldcode.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/fold/brace-fold.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/fold/xml-fold.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/comment/comment.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/addon/selection/active-line.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/css/css.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/xml/xml.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/clike/clike.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/javascript/javascript.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/css/css.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/dist/mode/htmlmixed/htmlmixed.js"),
                  CmsWorkplace.getStaticResourceUri("/editors/codemirror/js/htmlembedded_modified.js")};
          }
          getState().m_scriptURIs = JAVASCRIPT_URIS;
  
          addFunction("onBlur", new JavaScriptFunction() {
  
              private static final long serialVersionUID = 1L;
  
              @Override
              public void call(JsonArray arguments) {
  
                  onBlur(arguments.getString(0));
              }
          });
          addFunction("onChange", new JavaScriptFunction() {
  
              private static final long serialVersionUID = 1L;
  
              @Override
              public void call(JsonArray arguments) {
  
                  onChange(arguments.getString(0));
              }
          });
          addStyleName("o-codemirror");
      }
  
      /**
       * Adds a blur listener.<p>
       *
       * @param listener the blur listener
       */
      public void addBlurListener(FieldEvents.BlurListener listener) {
  
          addListener(FieldEvents.BlurEvent.class, listener, BLUR_METHOD);
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.v7.data.Property.ValueChangeNotifier#addListener(com.vaadin.v7.data.Property.ValueChangeListener)
       * @deprecated use {@link #addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
       */
      @Deprecated
      public void addListener(com.vaadin.v7.data.Property.ValueChangeListener listener) {
  
          addValueChangeListener(listener);
      }
  
      /**
       * @see com.vaadin.v7.data.Property.ValueChangeNotifier#addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)
       */
      public void addValueChangeListener(Property.ValueChangeListener listener) {
  
          addListener(Property.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD);
          // ensure "automatic immediate handling" works
          markAsDirty();
      }
  
      /**
       * Returns the font size.<p>
       *
       * @return the font size
       */
      public String getFontSize() {
  
          return getState().m_fontSize;
      }
  
      /**
       * @see com.vaadin.v7.data.Property#getType()
       */
      public Class<? extends String> getType() {
  
          return String.class;
      }
  
      /**
       * Returns the current editor content value.<p>
       *
       * @return the editor content value
       */
      public String getValue() {
  
          return m_codeValue;
      }
  
      /**
       * Returns whether auto close brackets is active.<p>
       *
       * @return <code>true</code> if auto close brackets is active
       */
      public boolean isCloseBrackets() {
  
          return getState().m_closeBrackets;
      }
  
      /**
       * Returns whether highlighting is active.<p>
       *
       * @return <code>true</code> if highlighting is active
       */
      public boolean isHighlighting() {
  
          return getState().m_highlighting;
      }
  
      /**
       * Returns whether line wrapping is active.<p>
       *
       * @return <code>true</code> if line wrapping is active
       */
      public boolean isLineWrapping() {
  
          return getState().m_lineWrapping;
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#isReadOnly()
       */
      @Override
      public boolean isReadOnly() {
  
          return super.isReadOnly();
      }
  
      /**
       * Returns whether tabs are visible.<p>
       *
       * @return <code>true</code> if tabs are visible
       */
      public boolean isTabsVisible() {
  
          return getState().m_tabsVisible;
      }
  
      /**
       * Registers the given buttons as the search and replace buttons.<p>
       *
       * @param search the search button
       * @param replace the replace button
       */
      public void registerSearchReplace(Button search, Button replace) {
  
          if (getState().m_enableSearchReplace) {
              throw new RuntimeException("Search/replace already registered.");
          }
          search.setId(HTML_ID_PREFIX + m_componentId + "-search");
          replace.setId(HTML_ID_PREFIX + m_componentId + "-replace");
          getState().m_enableSearchReplace = true;
          markAsDirty();
      }
  
      /**
       * Registers the given buttons as undo redo buttons.<p>
       *
       * @param undo the undo button
       * @param redo the redo button
       */
      public void registerUndoRedo(Button undo, Button redo) {
  
          if (getState().m_enableUndoRedo) {
              throw new RuntimeException("Undo/redo already registered.");
          }
          undo.setId(HTML_ID_PREFIX + m_componentId + "-undo");
          redo.setId(HTML_ID_PREFIX + m_componentId + "-redo");
          getState().m_enableUndoRedo = true;
          markAsDirty();
      }
  
      /**
       * Removes the given blur listener.<p>
       *
       * @param listener the listener to remove
       */
      public void removeBlurListener(FieldEvents.BlurNotifier listener) {
  
          removeListener(FieldEvents.BlurEvent.class, listener, BLUR_METHOD);
          // ensure "automatic immediate handling" works
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.v7.data.Property.ValueChangeNotifier#removeListener(com.vaadin.v7.data.Property.ValueChangeListener)
       * @deprecated use {@link #removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
       */
      @Deprecated
      public void removeListener(com.vaadin.v7.data.Property.ValueChangeListener listener) {
  
          removeValueChangeListener(listener);
      }
  
      /**
       * @see com.vaadin.v7.data.Property.ValueChangeNotifier#removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)
       */
      public void removeValueChangeListener(Property.ValueChangeListener listener) {
  
          removeListener(Property.ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD);
          markAsDirty();
      }
  
      /**
       * Sets the auto close brackets feature enabled.<p>
       *
       * @param closeBrackets <code>true</code> to auto close brackets
       */
      public void setCloseBrackets(boolean closeBrackets) {
  
          getState().m_closeBrackets = closeBrackets;
          markAsDirty();
      }
  
      /**
       * Sets the editor font size.<p>
       *
       * @param fontSize the editor font size
       */
      public void setFontSize(String fontSize) {
  
          getState().m_fontSize = fontSize;
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#setHeight(float, com.vaadin.server.Sizeable.Unit)
       */
      @Override
      public void setHeight(float height, Unit unit) {
  
          super.setHeight(height, unit);
          getState().m_height = height + unit.getSymbol();
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#setHeight(java.lang.String)
       */
      @Override
      public void setHeight(String height) {
  
          super.setHeight(height);
          getState().m_height = height;
          markAsDirty();
      }
  
      /**
       * Sets the language highlighting enabled.<p>
       *
       * @param highlighting <code>true</code> to highlight
       */
      public void setHighlighting(boolean highlighting) {
  
          getState().m_highlighting = highlighting;
          markAsDirty();
      }
  
      /**
       * Sets the editor language.<p>
       *
       * @param codeMirrorLanguage the editor language
       */
      public void setLanguage(CodeMirrorLanguage codeMirrorLanguage) {
  
          getState().m_mode = codeMirrorLanguage.getLanguageName();
          markAsDirty();
      }
  
      /**
       * Sets line wrapping enabled.<p>
       *
       * @param lineWrapping <code>true</code> to wrap lines
       */
      public void setLineWrapping(boolean lineWrapping) {
  
          getState().m_lineWrapping = lineWrapping;
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#setReadOnly(boolean)
       */
      @Override
      public void setReadOnly(boolean readOnly) {
  
          super.setReadOnly(readOnly);
      }
  
      /**
       * Sets tab characters visible.<p>
       *
       * @param tabsVisible <code>true</code> to show tab characters
       */
      public void setTabsVisible(boolean tabsVisible) {
  
          getState().m_tabsVisible = tabsVisible;
          markAsDirty();
      }
  
      /**
       * Sets the editor theme.<p>
       *
       * @param codeMirrorTheme the editor theme
       */
      public void setTheme(CodeMirrorTheme codeMirrorTheme) {
  
          //   getState().m_id = m_componentId;
          getState().m_theme = codeMirrorTheme.getThemeName();
          markAsDirty();
      }
  
      /**
       * Sets the editor content value.<p>
       *
       * @param value the content value
       */
      public void setValue(String value) {
  
          m_codeValue = value;
  
          getState().m_contentValue = value;
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#setWidth(float, com.vaadin.server.Sizeable.Unit)
       */
      @Override
      public void setWidth(float width, Unit unit) {
  
          super.setWidth(width, unit);
          getState().m_width = width + unit.getSymbol();
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractComponent#setWidth(java.lang.String)
       */
      @Override
      public void setWidth(String width) {
  
          super.setWidth(width);
          getState().m_width = width;
          markAsDirty();
      }
  
      /**
       * @see com.vaadin.ui.AbstractJavaScriptComponent#getState()
       */
      @Override
      protected CodeMirrorState getState() {
  
          return (CodeMirrorState)super.getState();
      }
  
      /**
       * Called on blur.<p>
       *
       * @param value the content value
       */
      void onBlur(String value) {
  
          m_codeValue = value;
          fireEvent(new FieldEvents.BlurEvent(CmsCodeMirror.this));
      }
  
      /**
       * Called on blur.<p>
       *
       * @param value the content value
       */
      void onChange(String value) {
  
          boolean changed = !m_codeValue.equals(value);
          m_codeValue = value;
          if (changed) {
              fireEvent(new ValueChangeEvent(this));
          }
      }
  
      /**
       * Returns the code mirror localization JSON.<p>
       *
       * @param messages the message bundle to use
       *
       * @return the localization
       */
      private String getLocalizationMessages(CmsMessages messages) {
  
          JSONObject result = new JSONObject();
          try {
              result.put("search", messages.key(Messages.GUI_CODEMIRROR_LANG_SEARCH_0));
              result.put("hint", messages.key(Messages.GUI_CODEMIRROR_LANG_HINT_0));
              result.put("replace", messages.key(Messages.GUI_CODEMIRROR_LANG_REPLACE_0));
              result.put("replacewith", messages.key(Messages.GUI_CODEMIRROR_LANG_REPLACE_WITH_0));
              result.put("replaceconfirm", messages.key(Messages.GUI_CODEMIRROR_LANG_REPLACE_CONFIRM_0));
              result.put("replaceyes", messages.key(Messages.GUI_CODEMIRROR_LANG_REPLACE_YES_0));
              result.put("replaceno", messages.key(Messages.GUI_CODEMIRROR_LANG_RELACE_NO_0));
              result.put("replacestop", messages.key(Messages.GUI_CODEMIRROR_LANG_REPLACE_STOP_0));
  
              result.put("fontsize", messages.key(Messages.GUI_CODEMIRROR_LANG_FONT_SIZE_0));
          } catch (org.opencms.json.JSONException e) {
              // should never happen
          }
          return result.toString();
      }
  
      /**
       * Returns the localized short cut messages HTML.<p>
       *
       * @param messages the message bundle to use
       *
       * @return the HTML
       */
      private String getShortcutMessages(CmsMessages messages) {
  
          StringBuffer buffer = new StringBuffer();
          buffer.append("<span class=\"col1\"><b>").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_SEARCH_0));
          buffer.append("</b> ").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_SEARCH_HELP_0));
          buffer.append(" </span><span class=\"col2\"><b>").append(
              messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_FIND_NEXT_0));
          buffer.append("</b> ").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_FIND_NEXT_HELP_0));
          buffer.append(" </span><span class=\"col3\"><b>").append(
              messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_FIND_PREVIOUS_0));
          buffer.append("</b> ").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_FIND_PREVIOUS_HELP_0));
          buffer.append(" </span><span class=\"col1\"><b>").append(
              messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_REPLACE_0));
          buffer.append("</b> ").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_REPLACE_HELP_0));
          buffer.append(" </span><span class=\"col2\"><b>").append(
              messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_REPLACE_ALL_0));
          buffer.append("</b> ").append(messages.key(Messages.GUI_CODEMIRROR_SHORTCUT_REPLACE_ALL_HELP_0)).append(
              "</span>");
          return buffer.toString();
      }
  }
  