Skip to content

Instantly share code, notes, and snippets.

@furnox
Created February 3, 2020 18:22
Show Gist options
  • Save furnox/3e374f3a3e71979ed7fa7885158c0a6a to your computer and use it in GitHub Desktop.
Save furnox/3e374f3a3e71979ed7fa7885158c0a6a to your computer and use it in GitHub Desktop.
Tool to show git and Subversion annotations (blame) in the file
/*
komodo tool: SCC Annotations
============================
async: 0
is_clean: true
keyboard_shortcut: Ctrl+K, Ctrl+A
language: JavaScript
rank: 100
trigger: trigger_postopen
trigger_enabled: 0
type: macro
version: 1.1.6
============================*/
/**
* Macro to show source code control annotate information in the left scintilla
* margin.
*/
// Scintilla margin parameters.
const MARGIN_WIDTH = 268; // pixels wide
const MARGIN = 4; // The unused scintilla margin.
const koLogger = require("ko/logging");
const log = koLogger.getLogger("macro.scc_annotate");
//log.setLevel(koLogger.LOG_DEBUG);
/**
* Parse git annotate line, return array of annotate entries [date, author, rev]
*/
var GitAnnotateParser = function(stdout) {
var lines = stdout.split("\n");
var line_split;
var revision, date, author;
var result = [];
for (var lineno=0; lineno < lines.length; lineno++) {
line_split = lines[lineno].match(/\s*(\w+).*?\((.*?)([-\d]+).*?\).*/);
if (!line_split) {
result.push("");
continue;
}
revision = line_split[1];
author = line_split[2];
date = line_split[3];
if (date && author) {
result.push([date, author, revision]);
} else {
result.push("");
}
}
return result;
};
/**
* Parse svn annotate line, return array of annotate entries [date, author, rev]
*/
var SvnAnnotateParser = function(stdout) {
var lines = stdout.split("\n");
var line_split;
var revision, author, date;
var result = [];
for (var lineno=0; lineno < lines.length; lineno++) {
line_split = lines[lineno].split(/\s+/, 4);
if (!line_split[0]) {
line_split = line_split.slice(1);
}
revision = line_split[0];
author = line_split[1];
date = line_split[2];
if (revision && author && date) {
result.push([date, author, revision]);
}
}
return result;
};
/**
* Fetch annotate lines for the given file.
*/
var FetchAnnotationLines = function(koFileEx) {
log.debug("FetchAnnotationLines for file " + koFileEx.path);
if (!koFileEx.isLocal || !koFileEx.isFile) {
throw new Exception("Annotate only works on local files");
}
var sccType = koFileEx.sccType;
if (!sccType) {
throw new Exception("Annotate only works on files under scc control");
}
if (sccType != "svn" && sccType != "git") {
throw new Exception("Annotate only works for Git or Subversion repositories");
}
var scc_annotate_parser = GitAnnotateParser;
if (sccType == "svn") {
scc_annotate_parser = SvnAnnotateParser;
}
var sccSvc = Components.classes["@activestate.com/koSCC?type=" + sccType + ";1"].
getService(Components.interfaces.koISCC);
var scc_executable = sccSvc.executable || sccSvc.name;
var filename = koFileEx.leafName;
// Double quote the paths on Windows.
if (navigator.platform.startsWith("Win")) {
scc_executable = '"' + scc_executable + '"';
filename = '"' + filename + '"';
}
var cmd = scc_executable + ' annotate ' + filename;
var runSvc = Components.classes["@activestate.com/koRunService;1"].
getService(Components.interfaces.koIRunService);
var process = runSvc.RunAndNotify(cmd, koFileEx.dirName, '', '');
var retval = process.wait(-1); /* wait till the process is done */
if (retval == 0) {
return scc_annotate_parser(process.getStdout());
} else {
throw Error(process.getStderr());
}
return [];
};
/**
* Generate a unique color style for this entry.
*
* TODO: Be smarter - check both previous and next entries to ensure the color
* is different between changes.
*/
var GetUniqueStyle = function(sm, entry_styles, entry) {
// Default color for this author.
var entryS = entry.toString();
var style_num = entry_styles[entryS];
if (style_num !== undefined) {
return style_num;
}
// Create a color for this entry.
style_num = 0;
for (var item in entry_styles) {
style_num += 1;
}
style_num = (style_num % 8);
entry_styles[entryS] = style_num;
return style_num;
};
/**
* Show the annotations in the margin.
*/
var DisplayAnnotations = function(view, lines) {
log.debug("DisplayAnnotations for " + lines.length + " lines");
const MARGIN_STYLE_OFFSET = 255;
/** @type Components.interfaces.ISciMoz */
var sm = view.scimoz;
sm.setMarginTypeN(MARGIN, sm.SC_MARGIN_TEXT); // left-justified text
sm.setMarginWidthN(MARGIN, MARGIN_WIDTH);
sm.setMarginSensitiveN(MARGIN, true); // Allow mouse clicks.
sm.marginStyleOffset = MARGIN_STYLE_OFFSET;
var isDarkScheme = view.scheme.isDarkBackground;
// Scintilla colors are in BGR format.
sm.styleSetBack(MARGIN_STYLE_OFFSET+1, isDarkScheme ? 0x442200 : 0xBBDDFF);
sm.styleSetBack(MARGIN_STYLE_OFFSET+2, isDarkScheme ? 0x220044 : 0xDDFFBB);
sm.styleSetBack(MARGIN_STYLE_OFFSET+3, isDarkScheme ? 0x004422 : 0xFFBBDD);
sm.styleSetBack(MARGIN_STYLE_OFFSET+4, isDarkScheme ? 0x000022 : 0xFFFFDD);
sm.styleSetBack(MARGIN_STYLE_OFFSET+5, isDarkScheme ? 0x002200 : 0xFFDDFF);
sm.styleSetBack(MARGIN_STYLE_OFFSET+6, isDarkScheme ? 0x220000 : 0xDDFFFF);
sm.styleSetBack(MARGIN_STYLE_OFFSET+7, isDarkScheme ? 0x440044 : 0xBBBBBB);
sm.styleSetBack(MARGIN_STYLE_OFFSET+8, isDarkScheme ? 0x220022 : 0xDDDDDD);
var entry, style, entry_styles = {};
for (var lineno=0; lineno < lines.length; lineno++) {
entry = lines[lineno];
if (!entry) {
continue;
}
style = GetUniqueStyle(sm, entry_styles, entry);
sm.marginSetText(lineno, entry[2] + ': ' + entry[0] + ': ' + entry[1].slice(0,10));
sm.marginSetStyle(lineno, style);
}
};
/**
* Handle annotate margin click and launch scc history dialog when clicked.
*/
var OnMarginClick = function(modifiers, position, margin) {
log.debug("OnMarginClick:: margin " + margin);
var view = ko.views.manager.currentView;
if (margin != MARGIN) {
view._annotate_orig_onMarginClick(modifiers, position, margin);
}
var lineno = view.scimoz.lineFromPosition(position);
var revision = view._annotate_lines[lineno][2];
ko.commands.doCommand("cmd_SCChistory_File", revision);
};
/**
* Generate and show the annotations.
*/
var ShowAnnotations = function(view) {
log.debug("ShowAnnotations");
var lines = FetchAnnotationLines(view.koDoc.file);
DisplayAnnotations(view, lines);
view._annotate_lines = lines;
view._annotate_orig_onMarginClick = view.onMarginClick;
view.onMarginClick = OnMarginClick;
};
/**
* Remove (hide) existing annotations.
*/
var RemoveAnnotations = function(view) {
log.debug("RemoveAnnotations");
view.scimoz.setMarginWidthN(MARGIN, 0);
view.onMarginClick = view._annotate_orig_onMarginClick;
delete view._annotate_orig_onMarginClick;
delete view._annotate_lines;
};
try {
var view = ko.views.manager.currentView;
if (view) {
if (view.scimoz.getMarginWidthN(MARGIN) > 0) {
// Already visible - hide it.
RemoveAnnotations(view);
} else {
ShowAnnotations(view);
}
}
} catch (ex) {
log.exception(ex);
ko.statusBar.AddMessage("annotate error" + ex.toString(), "macros", 5000, true);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment