Column Indentation Guide on Textmate - textmate

I'd like to know if there's a bundle or a preference somewhere in Textmate to get Sublime's white dotted column delimiter. Look at the screenshots.
Look at this PHP function in Textmate
(source: darwinsantos.com)
Now look at it in Sublime.
(source: darwinsantos.com)
If you take a close look notice that in Sublime the beginning and ending curly brace are bound by a white dotted line that let's you know that both curly braces are aligned in the exact same column.
Is there a way to get this in Textmate?

Update (5/2016): TextMate has gotten indent guides! As of version 2.0-beta.9.2 View->Show Indent Guides. They're a work in progress, but they are available.
Update: If you're able to get this working and are willing to build your own textmate via the official instructions, then you might have a crack a building (and maybe even contributing to) my fold guides enabled version of TextMate2. There are no builds, and it is not ready to be introduced into TextMate2 yet as it lacks a setting to disable the guides.
This is a feature in development, when complete it will be significantly more intelligent than what I'm about to describe. The new version, when it eventually comes out, will respect the indentation rules of the language, rather than simply filling in pairs of spaces/tabs.
That said, I've used this to ensure countless lines of templates are perfect.
The method is updated, but otherwise the same as described for Textmate1 by Cocabits.
You will end up with something like this:
Note the second to last line, lacking the white space to trigger the lines. The new version will be much closer to Sublime's
First we are going to need to teach TextMate how to identify the tabs and spaces which we use before each line of code.
I have created a fold guides bundle however this is the first time I've given it out and I am terrified it just won't work for you, that said give it a try.
If it doesn't work, you will need to manually add these rules, I will show you how to make it its own bundle, but you could add it directly to any language you like.
Create a bundle from Bundles->Edit Bundles, then, File->New, select bundle and give it a name, then File->New and make a grammar. The grammar should have this code:
{ patterns = (
{ include = '#leading-spaces'; },
{ name = 'meta.leading-tabs';
begin = '^(?=\t)';
end = '(?=[^\t])';
patterns = (
{ match = '(\t)(\t)?';
captures = {
1 = { name = 'meta.odd-tab'; };
2 = { name = 'meta.even-tab'; };
};
},
);
},
);
repository = {
leading-spaces = {
begin = '^(?=\s\s)';
end = '(?=[^\s\s])';
patterns = (
{ match = '(\s\s)(\s\s)?';
captures = {
1 = { name = 'meta.odd-tab'; };
2 = { name = 'meta.even-tab'; };
};
},
);
};
};
}
And the inspector should look like this:
Now we just need a theme rule to match 'meta.even-tab' and or 'meta.odd-tab', so just add this to your current theme:
{name = 'Alternating Tabs';
scope = 'meta.even-tab';
settings = {
background = '#232323';
};
}

Related

Get POSIX path of active Finder window with JXA AppleScript

I would like the JXA equivalent of this AppleScript snippet:
tell application "Finder"
# Get path
set currentTarget to target of window 1
set posixPath to (POSIX path of (currentTarget as alias))
# Show dialog
display dialog posixPath buttons {"OK"}
end tell
The closest I got was using the url property to initialize a Foundation NSURL object and access its fileSystemRepresentation property like so:
// Get path
var finder = Application('Finder')
var currentTarget = finder.finderWindows[0].target()
var fileURLString = currentTarget.url()
// I'd like to get rid of this step
var fileURL = $.NSURL.alloc.initWithString(fileURLString)
var posixPath = fileURL.fileSystemRepresentation
// Show dialog
finder.includeStandardAdditions = true
finder.displayAlert('', {buttons: ['Ok'], message: posixPath})
But this seems unnecessarily complex. Is there a nicer way to get to the POSIX path without using Foundation API or manual string wrangling?
If I naively try this:
finder.finderWindows[0].target().posixPath()
I get this error:
app.startupDisk.folders.byName("Users").folders.byName("kymer").folders.byName("Desktop").posixPath()
--> Error -1728: Can't get object.
This SO answer seems relevant, but I can't seem to adapt it to fit my needs:
App = Application.currentApplication()
App.includeStandardAdditions = true
SystemEvents = Application('System Events')
var pathToMe = App.pathTo(this)
var containerPOSIXPath = SystemEvents.files[pathToMe.toString()].container().posixPath()
Any help would be greatly appreciated!
The fact that such a simple piece of AppleScript code has no straightforward JXA translation is a testament to the sorry state of JXA and macOS automation based on OSA scripting in general:
foo's excellent answer contains helpful background information.
Another pointer is that the last time release notes were published was for OS X 10.11 (El Capitan) (as of this writing, we're on the verge of macOS 10.13 (High Sierra's) release).
This third-party July 2017 blog post announces more broadly that "that question was finally answered at WWDC last month: Apple has abandoned its automation technologies, leaving them to wither and die."
As your own example suggests, among the two dying automation scripting languages AppleScript - despite all its warts - is the more mature, reliable choice.
To solve your problem in JXA, it looks like you've come up with the best approach yourself.
Let me package it as a helper function that perhaps easies the pain somewhat - to be clear: such a helper function should NOT be necessary:
// Helper function: Given a Finder window, returns its folder's POSIX path.
// Note: No need for an ObjC.import() statement, because NSURL is
// a Foundation class, and all Foundation classes are implicitly
// available.
function posixPath(finderWin) {
return $.NSURL.alloc.initWithString(finderWin.target.url()).fileSystemRepresentation
}
// Get POSIX path of Finder's frontmost window:
posixPath(Application('Finder').finderWindows[0])
In theory you'd write something like:
finder.finderWindows[0].target({as:"alias"})
but this doesn't work and there's nothing in the documentation to indicate it's supported. But this is SOP for JXA which, like Apple's earlier Scripting Bridge, suffers numerous design flaws and omissions, which never have (and likely never will be) fixed.[1]
FWIW, here's how you do it in Node.js, using NodeAutomation:
$ node
> Object.assign(this,require('nodeautomation'));undefined
> const fn = app('Finder')
> var file = fn.FinderWindows[0].target({asType:k.alias}) // returns File object
> file.toString() // converts File object to POSIX path string
'/Users/jsmith/dev/nodeautomation'
(Be aware that NodeAutomation is a very low-priority project for me, given that Mac Automation looks to be pretty much on its last legs at Apple. Caveat emptor, etc. For non-trivial scripting I strongly recommend sticking to AppleScript as it's the only officially supported solution that actually works right.)
[1] For instance, another JXA limitation is that most apps' move and duplicate commands are seriously crippled cos the JXA authors forgot to implement insertion reference forms. (BTW, I reported all these problems before JXA was even released, and appscript had all this stuff solved a decade ago, so they've no excuse for not getting it right either.)
#Kymer, you said:
But this seems unnecessarily complex. Is there a nicer way to get to
the POSIX path without using Cocoa API or manual string wrangling?
You're on the right track. Here's the best method I know of. If there are better, I too would like to know about them. But, this seems to work well as fast, and works for both files and folders.
var finderApp = Application("Finder");
var itemList = finderApp.selection();
var oItem = itemList[0];
var oItemPaths = getPathInfo(oItem);
/* --- oItemPaths Object Keys ---
oItemPaths.itemClass
oItemPaths.fullPath
oItemPaths.parentPath
oItemPaths.itemName
*/
console.log(JSON.stringify(oItemPaths, undefined, 4))
function getPathInfo(pFinderItem) {
var itemClass = pFinderItem.class(); // returns "folder" if item is a folder.
var itemURL = pFinderItem.url();
var fullPath = decodeURI(itemURL).slice(7);
//--- Remove Trailing "/", if any, to handle folder item ---
var pathElem = fullPath.replace(/\/$/,"").split('/')
var itemName = pathElem.pop();
var parentPath = pathElem.join('/');
return {
itemClass: itemClass,
fullPath: fullPath,
parentPath: parentPath,
itemName: itemName
};
}
Here's a fairly simple example function that just grabs the window's target and then strips off the leading file:// from its url.
/*
pathToFrontWindow()
returns path to front Finder window
*/
function pathToFrontWindow() {
if ( finder.windows.length ) {
return decodeURI( finder.windows[0].target().url().slice(7) )
} else {
return ""
}
}
(() => {
// getFinderDirectory :: () -> String
const getFinderDirectory = () =>
Application('Finder')
.insertionLocation()
.url()
.slice(7);
return getFinderDirectory();
})();

How to get the entire Visual Studio active document... with formatting

I know how to use VS Extensibility to get the entire active document's text. Unfortunately, that only gets me the text and doesn't give me the formatting, and I want that too.
I can, for example, get an IWpfTextView but once I get it, I'm not sure what to do with it. Are there examples of actually getting all the formatting from it? I'm only really interested in text foreground/background color, that's it.
Note: I need the formatted text on every edit, so unfortunately doing cut-and-paste using the clipboard is not an option.
Possibly the simplest method is to select all of the text and copy it to the clipboard. VS puts the rich text into the clipboard, so when you paste, elsewhere, you'll get the colors (assuming you handle rich text in your destination).
Here's my not-the-simplest solution. TL;DR: you can jump to the code at https://github.com/jimmylewis/GetVSTextViewFormattedTextSample.
The VS editor uses "classifications" to show segments of text which have special meaning. These classifications can then be formatted differently according to the language and user settings.
There's an API for getting the classifications in a document, but it didn't work for me. Or other people, apparently. But we can still get the classifications through an ITagAggregator<IClassificationTag>, as described in the preceding link, or right here:
[Import]
IViewTagAggregatorFactoryService tagAggregatorFactory = null;
// in some method...
var classificationAggregator = tagAggregatorFactory.CreateTagAggregator<IClassificationTag>(textView);
var wholeBufferSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, 0, textBuffer.CurrentSnapshot.Length);
var tags = classificationAggregator.GetTags(wholeBufferSpan);
Armed with these, we can rebuild the document. It's important to note that some text is not classified, so you have to piece everything together in chunks.
It's also notable that at this point, we have no idea how any of these tags are formatted - i.e. the colors used during rendering. If you want to, you can define your own mapping from IClassificationType to a color of your choice. Or, we can ask VS for what it would do using an IClassificationFormatMap. Again, remember, this is affected by user settings, Light vs. Dark theme, etc.
Either way, it could look something like this:
// Magic sauce pt1: See the example repo for an RTFStringBuilder I threw together.
RTFStringBuilder sb = new RTFStringBuilder();
var wholeBufferSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, 0, textBuffer.CurrentSnapshot.Length);
// Magic sauce pt2: see the example repo, but it's basically just
// mapping the spans from the snippet above with the formatting settings
// from the IClassificationFormatMap.
var textSpans = GetTextSpansWithFormatting(textBuffer);
int currentPos = 0;
var formattedSpanEnumerator = textSpans.GetEnumerator();
while (currentPos < wholeBufferSpan.Length && formattedSpanEnumerator.MoveNext())
{
var spanToFormat = formattedSpanEnumerator.Current;
if (currentPos < spanToFormat.Span.Start)
{
int unformattedLength = spanToFormat.Span.Start - currentPos;
SnapshotSpan unformattedSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentPos, unformattedLength);
sb.AppendText(unformattedSpan.GetText(), System.Drawing.Color.Black);
}
System.Drawing.Color textColor = GetTextColor(spanToFormat.Formatting.ForegroundBrush);
sb.AppendText(spanToFormat.Span.GetText(), textColor);
currentPos = spanToFormat.Span.End;
}
if (currentPos < wholeBufferSpan.Length)
{
// append any remaining unformatted text
SnapshotSpan unformattedSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentPos, wholeBufferSpan.Length - currentPos);
sb.AppendText(unformattedSpan.GetText(), System.Drawing.Color.Black);
}
return sb.ToString();
Hope this helps with whatever you're doing. The example repo will ask if you you want the formatted text in the clipboard after each edit, but that was just a dirty way that I could test and see that it worked. It's annoying, but it was just a PoC.

How to indent the first line of a paragraph in CKEditor

I'm using CKEditor and I want to indent just the first line of the paragraph. What I've done before is click "Source" and edit the <p> style to include text-indent:12.7mm;, but when I click "Source" again to go back to the normal editor, my changes are gone and I have no idea why.
My preference would be to create a custom toolbar button, but I'm not sure how to do so or where to edit so that clicking a custom button would edit the <p> with the style attribute I want it to have.
Depending on which version of CKE you use, your changes most likely disappear because ether the style attribute or the text-indent style is not allowed in the content. This is due to the Allowed Content Filter feature of CKEditor, read more here: http://docs.ckeditor.com/#!/guide/dev_advanced_content_filter
Like Ervald said in the comments, you can also use CSS to do this without adding the code manually - however, your targeting options are limited. Either you have to target all paragraphs or add an id or class property to your paragraph(s) and target that. Or if you use a selector like :first-child you are restricted to always having the first element indented only (which might be what you want, I don't know :D).
To use CSS like that, you have to add the relevant code to contents.css, which is the CSS file used in the Editor contents and also you have to include it wherever you output the Editor contents.
In my opinion the best solution would indeed be making a plugin that places an icon on the toolbar and that button, when clicked, would add or remove a class like "indentMePlease" to the currently active paragraph. Developing said plugin is quite simple and well documented, see the excellent example at http://docs.ckeditor.com/#!/guide/plugin_sdk_sample_1 - if you need more info or have questions about that, ask in the comments :)
If you do do that, you again need to add the "indentMePlease" style implementation in contents.css and the output page.
I've got a way to indent the first line without using style, because I'm using iReport to generate automatic reports. Jasper does not understand styles. So I assign by jQuery an onkeydown method to the main iframe of CKEditor 4.6 and I check the TAB and Shift key to do and undo the first line indentation.
// TAB
$(document).ready(function(){
startTab();
});
function startTab() {
setTimeout(function(){
var $iframe_document;
var $iframe;
$iframe_document = $('.cke_wysiwyg_frame').contents();
$iframe = $iframe_document.find('body');
$iframe.keydown(function(e){
event_onkeydown(e);
});
},300);
}
function event_onkeydown(event){
if(event.keyCode===9) { // key tab
event.preventDefault();
setTimeout(function(){
var editor = CKEDITOR.instances['editor1'], //get your CKEDITOR instance here
range = editor.getSelection().getRanges()[0],
startNode = range.startContainer,
element = startNode.$,
parent;
if(element.parentNode.tagName != 'BODY') // If you take an inner element of the paragraph, get the parentNode (P)
parent = element.parentNode;
else // If it takes BODY as parentNode, it updates the inner element
parent = element;
if(event.shiftKey) { // reverse tab
var res = parent.innerHTML.toString().split(' ');
var aux = [];
var count_space = 0;
for(var i=0;i<res.length;i++) {
// console.log(res[i]);
if(res[i] == "")
count_space++;
if(count_space > 8 || res[i] != "") {
if(!count_space > 8)
count_space = 9;
aux.push(res[i]);
}
}
parent.innerHTML = aux.join(' ');
}
else { // tab
var spaces = " ";
parent.innerHTML = spaces + parent.innerHTML;
}
},200);
}
}

Broken toggle-comment in Textmate

I'm having a problem with the Toggle Comment command ("Comment Line / Selection") in TextMate for Actionscript 2 (I know, I know). I've tried completely stripping the language set down to isolate the issue, and tried walking through the Ruby, both to no avail. My issue is that the command insists on using block comments for comment toggling (⌘ + /) and doesn't respect when I add a preferences file to change TM_COMMENT_MODE. I even tried using this simple preference:
{ shellVariables = (
{ name = 'TM_COMMENT_START';
value = '// ';
},
);
}
but no luck. I'm hoping that someone who speaks Ruby much better than myself (ie. at all) can find a simple fix for this. You can reproduce in any (recent) install of TextMate by creating a new actionscript 2 file and trying to ⌘ + / a section of code (or even a line). Contrast to a JS file which will use a line comment. Copy the "Comments" snippet from JavaScript to Actionscript bundles, and the problem will persist.
Thanks!
In your ActionScript Bundle, add a Preference called "Comments". In the editor part, add:
{ shellVariables = (
{ name = 'TM_COMMENT_START';
value = '// ';
},
{ name = 'TM_COMMENT_DISABLE_INDENT';
value = 'YES';
},
{ name = 'TM_COMMENT_START_2';
value = '/* ';
},
{ name = 'TM_COMMENT_END_2';
value = '*/';
},
);
}
and finally at the bottom, set the scope selector to: scope.actionscript.2
Here is an image of what mine looks like
be sure to use the Reload Bundles menu item after you've made these changes.

CKEditor: How to stop angle brackets from converting to HTML entities

Our site is using tags like <# TAGNAME #> but CKEditor converts < and > to &lt and &gt which breaks these tags for use in our software.
I've discovered this option: config.protectedSource.push( /<#[\s\S]*##>/g ); which seems to stop the conversion if the data is saved from Source mode, but in WYSIWYG mode I can't find a way to stop the conversion. I've tried many options in their API but none of them seem to have helped, how can I fix this problem?
Were were looking at using CKEDitor to edit Smarty templates. The problem we were hitting was that it was replacing all the angle brackets and ampersands within the curly brackets, which messed everything up. This came up in a Google search so our solution should help anyone with similar issues.
CKEditor rebuilds the HTML every time you switch to Source mode and when you save, so you need to add to the HTML http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Data_Processor htmlFilter.
This worked for us:
//replace Form_content with whatever your editor's id is.
htmlParser = CKEDITOR.instances.Form_content.dataProcessor.htmlFilter;
//We don't want HTML encoding on smarty tags
//so we need to change things in curly brackets
htmlParser.onText = function(text) {
//find all bits in curly brackets
var matches = text.match(/\{([^}]+)\}/g);
//go through each match and replace the encoded characters
if (matches!=null) {
for (match in matches) {
var replacedString=matches[match];
replacedString = matches[match].replace(/>/g,'>');
replacedString = replacedString.replace(/</g,'<');
replacedString = replacedString.replace(/&/g,'&');
text = text.replace(matches[match],replacedString);
}
}
return text;
}
The onText function processes all the bits that aren't in tags or comments.
I'd imagine you can do something similar by altering the code above - I've left it as is as I think our problems and required solutions are very similar.
editor.on( 'mode', function(ev) {
if ( ev.editor.mode == 'source' ) {
var str=ev.editor.getData();
str=str.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, "\"");
ev.editor.textarea.setValue(str);
}
});
http://cksource.com/forums/viewtopic.php?f=11&t=20647&start=10
If you type < or > in any WYSIWYG editor, they will be converted to their HTML entities in source mode.

Resources