Malinda
Malinda

Reputation: 408

Syntax highlighting on android EditText using Span?

I created simple code for highlight syntax in EditText. First i created a HashMap to store keywords and colors.

                        Map<String,Integer> map = new HashMap<>();
                        map.put("public",Color.CYAN);
                        map.put("void", Color.BLUE);
                        map.put("String",Color.RED);

Then I added a TextWatcher for the EditText. In afterTextChanged method I used following code to set colors to each keyword,

                        ........
                        @Override
                        public void afterTextChanged(Editable editable) {
                            String string = editable.toString();
                            String[] split = string.split("\\s");
                            for(int i = 0 ; i < split.length ; i++){
                                String s = split[i];
                                if(map.containsKey(s)){
                                    int index = string.indexOf(s);
                                    int color = map.get(s);
                                    editable.setSpan(new ForegroundColorSpan(color),
                                            index,
                                            index + s.length(),
                                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                                }

                            }
                        }

This code is working, if i type different words, like "public void String", but It's not working when I type same word, "public public public". It only set color to the first word.

enter image description here

How can i make this work ? Thank you.

Upvotes: 5

Views: 3603

Answers (3)

Rupesh Kumar
Rupesh Kumar

Reputation: 59

As some answers are already accepted I would like to give an answer to a problem which I faced and that was when I pressed backspace and the word changes from "String" to "Strin" or "Srn"(backspace in midle) The color remains same where as it should be black as it is becoming non keyword. For that I have used the following code just after the snippet of the accepted answer. I hope it will help someone. Thanks!

@Override
            public void afterTextChanged(Editable s) {
                String string = s.toString();
                String[] split = string.split("\\s");

                int startIndex = 0;
                for(int i = 0 ; i < split.length ; i++){
                    String editable = split[i];
                    if(map.containsKey(editable)){

                        int index = string.indexOf(editable, startIndex);
                        int color = map.get(editable);
                        s.setSpan(new ForegroundColorSpan(color),
                                index,
                                index + editable.length(),
                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

                        startIndex = index + editable.length();
                    }
                    if(!map.containsKey(editable)) {
                        int index = string.indexOf(editable, startIndex);

                        s.setSpan(new ForegroundColorSpan(Color.BLACK),
                                index,
                                index + editable.length(),
                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }

                }

                }

Upvotes: 0

Jared Rummler
Jared Rummler

Reputation: 38121

Using string.indexOf(s) will get the first occurrence. Instead of having a map of keywords and using indexOf you could use a regular expression. I wrote this up real quick as an example:

Screenshot of example EditText below:

enter image description here

Example:

final EditText editText = new EditText(this);
editText.addTextChangedListener(new TextWatcher() {

  ColorScheme keywords = new ColorScheme(
      Pattern.compile(
          "\\b(package|transient|strictfp|void|char|short|int|long|double|float|const|static|volatile|byte|boolean|class|interface|native|private|protected|public|final|abstract|synchronized|enum|instanceof|assert|if|else|switch|case|default|break|goto|return|for|while|do|continue|new|throw|throws|try|catch|finally|this|super|extends|implements|import|true|false|null)\\b"),
      Color.CYAN
  );

  ColorScheme numbers = new ColorScheme(
      Pattern.compile("(\\b(\\d*[.]?\\d+)\\b)"),
      Color.BLUE
  );

  final ColorScheme[] schemes = { keywords, numbers };

  @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {

  }

  @Override public void onTextChanged(CharSequence s, int start, int before, int count) {

  }

  @Override public void afterTextChanged(Editable s) {
    removeSpans(s, ForegroundColorSpan.class);
    for (ColorScheme scheme : schemes) {
      for(Matcher m = scheme.pattern.matcher(s); m.find();) {
        s.setSpan(new ForegroundColorSpan(scheme.color),
            m.start(),
            m.end(),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
      }
    }
  }

  void removeSpans(Editable e, Class<? extends CharacterStyle> type) {
    CharacterStyle[] spans = e.getSpans(0, e.length(), type);
    for (CharacterStyle span : spans) {
      e.removeSpan(span);
    }
  }

  class ColorScheme {
    final Pattern pattern;
    final int color;

     ColorScheme(Pattern pattern, int color) {
      this.pattern = pattern;
      this.color = color;
    }
  }

});

Upvotes: 8

moon
moon

Reputation: 136

Since the start position for finding index always the same after each for loop, so I add a startIndex to record the changed start position, here the sample code:

public void afterTextChanged(Editable editable) {
    String string = editable.toString();
    String[] split = string.split("\\s");

    int startIndex = 0;
    for(int i = 0 ; i < split.length ; i++){
        String s = split[i];
        if(map.containsKey(s)){

            int index = string.indexOf(s, startIndex);
            int color = map.get(s);
            editable.setSpan(new ForegroundColorSpan(color),
                    index,
                    index + s.length(),
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            startIndex = index + s.length();
        }

    }
}

Upvotes: 6

Related Questions