package org.yinwang.pysonar.demos; import org.jetbrains.annotations.NotNull; import org.yinwang.pysonar.Indexer; import org.yinwang.pysonar.ast.*; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * Decorates Python source with style runs from the index. */ class Styler extends DefaultNodeVisitor { static final Pattern BUILTIN = Pattern.compile("None|True|False|NotImplemented|Ellipsis|__debug__"); /** * Matches the start of a triple-quote string. */ private static final Pattern TRISTRING_PREFIX = Pattern.compile("^[ruRU]{0,2}['\"]{3}"); private Indexer indexer; private String source; private String path; @NotNull private List<StyleRun> styles = new ArrayList<StyleRun>(); private Linker linker; /** Offsets of doc strings found by node visitor. */ @NotNull private Set<Integer> docOffsets = new HashSet<Integer>(); public Styler(Indexer idx, Linker linker) { this.indexer = idx; this.linker = linker; } /** * Entry point for decorating a source file. * @param path absolute file path * @param src file contents */ @NotNull public List<StyleRun> addStyles(String path, String src) { this.path = path; source = src; Module m = indexer.getAstForFile(path); if (m != null) { m.visit(this); } return styles; } @Override public boolean visit(@NotNull Name n) { Node parent = n.getParent(); if (parent instanceof FunctionDef) { FunctionDef fn = (FunctionDef)parent; if (n == fn.name) { addStyle(n, StyleRun.Type.FUNCTION); } else if (n == fn.kwarg || n == fn.vararg) { addStyle(n, StyleRun.Type.PARAMETER); } return true; } if (BUILTIN.matcher(n.getId()).matches()) { addStyle(n, StyleRun.Type.BUILTIN); return true; } return true; } @Override public boolean visit(Num n) { addStyle(n, StyleRun.Type.NUMBER); return true; } @Override public boolean visit(@NotNull Str n) { String s = sourceString(n.start, n.end); if (TRISTRING_PREFIX.matcher(s).lookingAt()) { addStyle(n.start, n.end - n.start, StyleRun.Type.DOC_STRING); docOffsets.add(n.start); // don't re-highlight as a string // highlightDocString(n); } return true; } private void addStyle(@NotNull Node e, int start, int len, StyleRun.Type type) { if (e.getFile() != null) { // if it's an NUrl, for instance addStyle(start, len, type); } } private void addStyle(@NotNull Node e, StyleRun.Type type) { addStyle(e, e.start, e.end - e.start, type); } private void addStyle(int begin, int len, StyleRun.Type type) { styles.add(new StyleRun(type, begin, len)); } private String sourceString(@NotNull Node e) { return sourceString(e.start, e.end); } private String sourceString(int begin, int end) { int a = Math.max(begin, 0); int b = Math.min(end, source.length()); b = Math.max(b, 0); try { return source.substring(a, b); } catch (StringIndexOutOfBoundsException sx) { // Silent here, only happens for weird encodings in file return ""; } } }