Package net.vektah.codeglance.render

Source Code of net.vektah.codeglance.render.Minimap$LineInfo

/*
* Copyright © 2013, Adam Scarr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.vektah.codeglance.render;

import com.intellij.lexer.Lexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.psi.tree.IElementType;
import net.vektah.codeglance.config.Config;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

/**
* A rendered minimap of a document
*/
public class Minimap {
  public BufferedImage img;
  public int height;
  private Logger logger = Logger.getInstance(getClass());
  private ArrayList<Integer> line_endings;
  private Config config;
  private static final Composite CLEAR = AlphaComposite.getInstance(AlphaComposite.CLEAR);
  private static final int[] unpackedColor = new int[4];
    private static final LineInfo NO_LINES = new LineInfo(1, 0, 0);

  public Minimap(Config config) {
    this.config = config;
  }

  /**
   * Scans over the entire document once to work out the required dimensions then rebuilds the image if necessary.
   *
   * Because java chars are UTF-8 16 bit chars this function should be UTF safe in the 2 byte range, which is all intellij
   * seems to handle anyway....
   */
  public void updateDimensions(CharSequence text, FoldRegion[] folding) {
    int line_length = 0;    // The current line length
    int longest_line = 1;   // The longest line in the document
    int lines = 1;          // The total number of lines in the document
    char last = 0;
    char ch;

    line_endings = new ArrayList<Integer>();
    // Magical first line
    line_endings.add(-1);

    for (int i = 0, len = text.length(); i < len; i++) {
      if(isFolded(i, folding)) {
        continue;
      }

      ch = text.charAt(i);

      if(ch == '\n' || (ch == '\r' && last != '\n')) {
        line_endings.add(i);
        lines++;
        if(line_length > longest_line) longest_line = line_length;
        line_length = 0;
      } else if (ch == '\t') {
        line_length += 4;
      } else {
        line_length++;
      }

      last = ch;
    }
    // If there is no final newline add one.
    if(line_endings.get(line_endings.size() - 1) != text.length() - 1) line_endings.add(text.length() - 1);

    height = lines * config.pixelsPerLine;

    // If the image is too small to represent the entire document now then regenerate it
    // TODO: Copy old image when incremental update is added.
    if (img == null || img.getHeight() < height || img.getWidth() < config.width) {
      if(img != null) img.flush();
      // Create an image that is a bit bigger then the one we need so we don't need to re-create it again soon.
      // Documents can get big, so rather then relative sizes lets just add a fixed amount on.
      img = new BufferedImage(config.width, height + 100 * config.pixelsPerLine, BufferedImage.TYPE_4BYTE_ABGR);
      logger.debug("Created new image");
    }
  }

  /**
   * @return the offset that a line starts at within the file.
   */
  public int getOffsetForLine(int line) {
    if (line < 1) {
      return line_endings.get(1);
    }

    if (line >= line_endings.size()) {
      return line_endings.get(line_endings.size() - 1);
    }

    return line_endings.get(line);
  }

  /**
   * Binary search for a line ending.
   * @param i character offset from start of document
   * @return 3 element array, [line_number, o]
   */
  public LineInfo getLine(int i) {
        // We can get called before the line scan has been done. Just return the first line.
        if (line_endings == null) {
            return NO_LINES;
        }
    int lines = line_endings.get(line_endings.size() - 1);
    if(i > lines) i = lines;
    if(i < 0) i = 0;
    // Dummy entries if there are no lines
    if(line_endings.size() == 0) return NO_LINES;
    if(line_endings.size() == 1) return NO_LINES;
    if(line_endings.size() == 2) return new LineInfo(1, line_endings.get(0) + 1, line_endings.get(1));

    int index_min = 0;
    int index_max = line_endings.size() - 1;
    int index_mid;
    int value;

    while(true) {
      index_mid = (int) Math.floor((index_min + index_max) / 2.0f); // Key space is pretty linear, might be able to use that to scale our next point.
      value = line_endings.get(index_mid);

      if(value < i) {
        if(i < line_endings.get(index_mid + 1)) return new LineInfo(index_mid + 1, value + 1, line_endings.get(index_mid + 1));

        index_min = index_mid + 1;
      } else if(i < value) {
        if(line_endings.get(index_mid - 1) < i) return new LineInfo(index_mid, line_endings.get(index_mid - 1) + 1, value);

        index_max = index_mid - 1;
      } else {
        // character at i is actually a newline, so grab the line before it.
        return new LineInfo(index_mid, line_endings.get(index_mid - 1) + 1, i);
      }
    }
  }

  /**
   * Works out the color a token should be rendered in.
   *
   * @param element       The element to get the color for
   * @param hl            the syntax highlighter for this document
   * @param colorScheme   the users color scheme
   * @return the RGB color to use for the given element
   */
  private int getColorForElementType(IElementType element, SyntaxHighlighter hl, EditorColorsScheme colorScheme) {
    int color = colorScheme.getDefaultForeground().getRGB();
    Color tmp;
    TextAttributesKey[] attributes = hl.getTokenHighlights(element);
    for(TextAttributesKey attribute : attributes) {
      TextAttributes attr = colorScheme.getAttributes(attribute);
      if(attr != null) {
        tmp = attr.getForegroundColor();
        if(tmp != null) color = tmp.getRGB();
      }
    }

    return color;
  }

  /**
   * Checks if a given position is within a folded region
   * @param position  the offset from the start of file in chars
   * @param regions   the array of regions to check against
   * @return true if the given position is folded.
   */
  private boolean isFolded(int position, FoldRegion[] regions) {
    for (FoldRegion region: regions) {
      if (!region.isExpanded() && region.getStartOffset() < position && position < region.getEndOffset()) {
        return true;
      }
    }

    return false;
  }

  /**
   * Internal worker function to update the minimap image
   *
   * @param text          The entire text of the document to render
   * @param colorScheme   The users color scheme
   * @param hl            The syntax highlighter to use for the language this document is in.
   */
  public void update(CharSequence text, EditorColorsScheme colorScheme, SyntaxHighlighter hl, FoldRegion[] folding) {
    logger.debug("Updating file image.");
    updateDimensions(text, folding);

    int color;
    int bgcolor = colorScheme.getDefaultBackground().getRGB();
    char ch;
    LineInfo startLine;
    float topWeight;
    float bottomWeight;
    Lexer lexer = hl.getHighlightingLexer();
    IElementType tokenType;

    Graphics2D g = (Graphics2D)img.getGraphics();
    g.setComposite(CLEAR);
    g.fillRect(0, 0, img.getWidth(), img.getHeight());

    lexer.start(text);
    tokenType = lexer.getTokenType();

    int x, y;
    while(tokenType != null) {
      int start = lexer.getTokenStart();
      startLine = getLine(start);
      y = startLine.number * config.pixelsPerLine;

      color = getColorForElementType(tokenType, hl, colorScheme);

      // Pre-loop to count whitespace from start of line.
      x = 0;
      for (int i = startLine.begin; i < start; i++) {
        // Dont count lines inside of folded regions.
        if (isFolded(i, folding)) {
          continue;
        }

        if(text.charAt(i) == '\t') {
          x += 4;
        } else {
          x += 1;
        }

        // Abort if this line is getting to long...
        if(x > config.width) break;
      }

      // Render whole token, make sure multi lines are handled gracefully.
      for(int i = start; i < lexer.getTokenEnd(); i++) {
        // Don't render folds.
        if (isFolded(i, folding)) {
          continue;
        }

        ch = text.charAt(i);

        if(ch == '\n') {
          x = 0;
          y += config.pixelsPerLine;
        } else if(ch == '\t') {
          x += 4;
        } else {
          x += 1;
        }

        topWeight = CharacterWeight.getTopWeight(text.charAt(i));
        bottomWeight = CharacterWeight.getBottomWeight(text.charAt(i));

        // No point rendering non visible characters.
        if(topWeight == 0) continue;

        if(0 <= x && x < img.getWidth() && 0 <= y && y + config.pixelsPerLine < img.getHeight()) {
          switch(config.pixelsPerLine) {
            case 1:
              // Cant show whitespace between lines any more. This looks rather ugly...
              setPixel(x,  y + 1, color, (float) ((topWeight + bottomWeight) / 2.0));
              break;

            case 2:
              // Two lines we make the top line a little lighter to give the illusion of whitespace between lines.
              setPixel(x, y, color, topWeight * 0.5f);
              setPixel(x, y + 1, color, bottomWeight);
              break;
            case 3:
              // Three lines we make the top nearly empty, and fade the bottom a little too
              setPixel(x, y, color, topWeight * 0.3f);
              setPixel(x, y + 1, color, (float) ((topWeight + bottomWeight) / 2.0));
              setPixel(x, y + 2, color, bottomWeight * 0.7f);
              break;
            case 4:
              // Empty top line, Nice blend for everything else
              setPixel(x, y + 1, color, topWeight);
              setPixel(x, y + 2, color, (float) ((topWeight + bottomWeight) / 2.0));
              setPixel(x, y + 3, color, bottomWeight);
          }
        }
      }

      lexer.advance();
      tokenType = lexer.getTokenType();
    }
  }

  /**
   * mask out the alpha component and set it to the given value.
   * @param color         Color A
   * @param alpha     alpha percent from 0-1.
   * @return int color
   */
  private void setPixel(int x, int y, int color, float alpha) {
    if(alpha > 1) alpha = color;
    if(alpha < 0) alpha = 0;

    // abgr is backwards?
    unpackedColor[3] = (int) (alpha * 255);
    unpackedColor[0] = (color & 0xFF0000) >> 16;
    unpackedColor[1] = (color & 0x00FF00) >> 8;
    unpackedColor[2] = (color & 0x0000FF);

    img.getRaster().setPixel(x, y, unpackedColor);
  }

  public static class LineInfo {
    LineInfo(int number, int begin, int end) {
      this.number = number;
      this.begin = begin;
      this.end = end;
    }

    public int number;
    public int begin;
    public int end;
  }
}
TOP

Related Classes of net.vektah.codeglance.render.Minimap$LineInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.