Insights and discoveries
from deep in the weeds
Outsharked

Tuesday, October 8, 2019

WSL, Windows Terminal, almost bliss

You thought this blog was dead? Ha! Never!


I have to admit admit, using this "blogger" editor for the first time in several years feels pretty retro. It's the same feeling I got the last time I tried to use a rotary phone. I'm honestly a bit surprised everything is still as I left it so long ago, and it still seems to work. But hey, what's old is new/cool again, right? Or something. Yeah, the format is crap. I'm thinking about adding an "under construction" animated gif to round it out.

Anyway, I'm using a Windows machine full-time again after a couple years using a MacBook. This mostly makes me happy. Luckily, the worst thing about the Windows software developer experience - the wretched console/shell situation - has improved somewhat. Not completely there yet, but it was far easier to set myself up with a basically high-functioning linux shell for all my console keyboarding, and be able to use Visual Studio Code and so on. Here's what I did.

1) Set up WSL (Windows Subsystem for Linux)
2) Install Microsoft Windows Terminal. The easiest way is with Chocolatey:
> choco install microsoft-windows-terminal

Note: Windows Terminal is definitely pre-beta software, but I've been using it a couple weeks without incident. While it lacks a lot of config options so far, it's fast, seems to avoid all the jank and complexity of ConEmu, and basically... just works.

3) Install windows-terminal-quake, or my not-yet-mainlined fork here. This is a helper that adds a vital missing UX feature to Windows Terminal - hotkey access. CTRL+~ will open/close the terminal in quake style; or bring to the foreground if it's not. This will most likely go away soon; this is a much requested feature so it seems likely it will become part of WT before long.

4) Install oh-my-zsh in your wsl environment:
> sudo apt install zsh
> ssh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

5) Add windows-style CLI editing capability to your wsl zsh shell with this gist. This lets you use familiar windows keyboard combinations for editing inline - e.g. shift+left/right to select text, ctrl+shift+left/right to select by word, home/end to move to start or end of the line, etc.

6) If you want, change your keybindings in Windows Terminal so CTRL+V is "paste." The default is CTRL+SHIFT+V. Since I rarely use the native bash CTRL+V this doesn't do much for me; I'd rather CTRL+V work the same everywhere. So just rebind it. :
{
  "command": "paste",
  "keys": [ "ctrl+v" ]
}

But what about copy? CTRL+C will already copy text when you are editing inline in zsh without any config changes to Windows Terminal if you did step #5. That script will automatically bind CTRL+C to copy the selected text in a command line to the Windows clipboard -- but only while zsh is within the interactive CLI.

When a command is running, CTRL+C will still send an interrupt and break the running program. This results in the very natural (to me) behavior of CTRL+C copying when I expect it to, and breaking when I expect it to.

You can also copy text from outside the command line using the mouse within Windows Terminal's native behavior. Just click-select something and right-click it, and it will be coped. WT handles pasting in every context; no special config needed here other than to change the keybinding.



That's about it! I can pretty much do everything I want: 
  • Use a real linux CLI environment
  • Copy/Paste work as expected
  • I can edit text in the interactive CLI as I would elsewhere in windows
  • Quake!
  • Just type `code .` to launch vscode to work in the current wsl directory



Friday, June 12, 2015

Using Color Theme Editor in Visual Studio 2015 RC

For inexplicable reasons, Microsoft has put out a release candidate for Visual Studio 2015 without updating the color theme editor. 


This may seem like a minor point, but most developers I know are somewhat particular about their development environment. We go to great pains to make it look and feel exactly the way we want to. There are so many scenarios that can be configured. It can take a long time to get things just the way you like them.

So when I first fired up VS 2015 RC1 and discovered that it looked like Visual Studio 2010, and there was no theme editor extension, I just shut it down and figured I'd wait it out. I was using a custom theme as my starting point, and had heavily customized it from there. If it was even possible to replicate it without the theme editor, I didn't know where to begin.

There is a solution.

Actually, it's one I've written about before.

You can use the VS2013 color theme editor extension in VS2015. It doesn't work perfectly, but I was able to migrate my settings from VS2013 without too much trouble, and once that's done you don't need to touch it again.


1. Export settings


Fire up VS2013. 

  • Choose Tools -> Import and Export Settings.
  • Select "Export selected environmental settings" and click Next.
  • Unselect everything by clicking the partially-checked box next to All Settings.
  • Expand  Options, Environment and check Fonts and Colors, and click Next.
  • Change the target path if desired, and click Finish to export your color settings.
If you are using a custom theme from the VS color theme editor, you need to export it too.
  • Choose Tools -> Customize Colors. 
  • Select the them you are using. In my case it's a custom theme called Monokai. 
  • Click the Edit Theme icon (the center one).
  • Click the "export" icon (circled in the picture to the right).
  • Change the file type to "Theme Package Definitions (*.pkgdef)" and save it somewhere. You're done with this part.

2. Migrate the extension


Following the same instructions from two and a half years ago that I wrote up to migrate Ultrafind, migrate the color theme editor extension to VS2015. This time around the source is 12.0, the target is 14.0, and the extension is in a folder called 1rdkntxh.iwn (at least on my computer, it was pretty obvious which it was just by peeking in the folders). Copy this thing to the 14.0 folder.

If you exported a custom theme as I did above, copy the .pkgdef file you exported into the Colors subfolder where you copied the extension itself. This isn't where custom themes lived before, but rather promotes it to an "installed theme." This was necessary as the "import theme" feature doesn't seem to work properly under VS2015, however, just putting it there works fine.

3. Enable it


Start Visual Studio 2015, and choose Tools -> Extensions and Updates. Select "Visual Studio 2013 Color Theme Editor" and click Enable. Restart VS as directed.

4. Import old settings


The theme editor should load up right away. Select your theme of choice.

Now import your general fonts & color settings that you previously exported using Tools -> Import and Export Settings

5. Awesome! Now I can function again. 

... so does it work for other stuff I can't live without too?


Maybe?

I tried Productivity Power Tools and it crashed as soon as I loaded a project.  Oh well.

As for Ultrafind? After it disappeared of the face of the earth a couple years ago, I eventually found a great alternative called Entrian Source Search -- not free but well worth the $29 price. In the year or so since I've been using it, it's probably cost me about $0.000001 per use. It's indispensible. 

Here's hoping this gets a VS2015 update soon.


Wednesday, May 15, 2013

Adding "enter key" handling to forms using Knockout.js & jQuery

Been a long time since my last post here, busy year! And man does this blog layout look dated.. priorities...

Here's a quick and easy solution to a problem for which I didn't find a complete solution elsewhere. A nice input form UI will let the user press the ENTER key to submit the form. Browsers don't have a predictable and consistent handling of this action, especially if there's more than one form on a page. Furthermore, if you aren't actually using a submit type element, browsers won't do anything as they have no way of knowing the intent anyway.

This extender for knockout.js + jQuery adds this functionality. All you need to do is add a binding for enterkey to an element that wraps your form, and pressing enter on any input control that's a descendent of the element containing the binding will cause that controller method to be invoked.

Example knockout bindings:
    <div id="form1" data-bind="enterkey: submit">
       Name: <input type=text data-bind="value: fullname">
       ... more fields ...

       <a href="#" data-bind="click: submit">Save</a>
    </div>

    <script type="text/javascript">
        var model = {
            fullname: ko.observable(),
            // ... more fields
            submit: function() {
                // process the form
            }
        };
        ko.applyBindings(model);
    </script>
The code:
ko.bindingHandlers.enterkey = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
       var allBindings = allBindingsAccessor();

            $(element).on('keypress', 'input, textarea, select', function (e) {
                var keyCode = e.which || e.keyCode;
                if (keyCode !== 13) {
                    return true;
                }

                var target = e.target;
                target.blur();

                allBindings.enterkey.call(viewModel, viewModel, target, element);

                return false;
            });
        }
    }
};
See an example on JsFiddle

Tuesday, October 16, 2012

CsQuery 1.3 Released

CsQuery 1.3 has been released. You can get it from NuGet or from the source repository on GitHub.

New HTML5 Compliant Parser

This release replaces the original HTML parser with the validator.nu HTML5 parser. This is a complete, standards-compliant HTML5 parser. This is the same codebase used in gecko-based web browsers (e.g. Firefox). You should expect excellent compatibility with the DOM that a web browser would render from markup. Problems that people have had in the past related to character set encoding, invalid HTML parsing, and other edge cases should simply go away.

In the process of implementing the new parser, some significant changes were made to the input and output API in order to take advantage of the its capabilities. While these revisions are generally backwards compatible with 1.2.1, there are a few potentially breaking changes. These can be summarized as follows:

  • DomDocument.DomRenderingOptions has been removed. The concept of assigning output options to a Document doesn't make sense any more (if it ever did); rather, you define options for how output is rendered at the time you render it.
  • IOutputFormatter interface has changed. This wasn't really used for anything before, so I doubt this will impact anyone, but it's conceivable that someone coded against it. The interface has been revised somewhat, and it is now used extensively to define a model for rendering output.

Hopefully, these changes won't impact you much or at all. But with this small price comes a host of new options for parsing and rendering HTML.

Create Method Options

In the beginning, there was but a single way to create a new DOM from HTML: Create. And it was good. But as the original parser evolved towards HTML5 compliance, the CreateFragment and CreateDocument methods were added, to define intent. Different rules apply depending on the context: a full document must always have an html tag (among others) for example. But you wouldn't want to add any missing tags if your intent was to create a fragment that was not supposed to stand alone.

The new parser has some more toys. It lets us define an expected document type (HTML5, HTML4 Strict, HTML4 Tranistional). We can tell it the context we expect out HTML to be found in when it starts parsing. We can choose to discard comments, and decide to permit self-closing XML tags. All of these things went into the Create method, allowing you complete control over how your input gets processed.

New Overloads

The basic Create method has overloads to accept a number of different kinds of input:

    public static CQ Create(string html)
    public static CQ Create(char[] html)
    public static CQ Create(TextReader html)
    public static CQ Create(Stream html)
    public static CQ Create(IDomObject element)
    public static CQ Create(IEnumerable<IDomObject> elements)

Additionally, there are similar overloads with parameters that let you control each option:


    public static CQ Create(string html, 
            HtmlParsingMode parsingMode =HtmlParsingMode.Auto, 
            HtmlParsingOptions parsingOptions = HtmlParsingOptions.Default,
            DocType docType = DocType.Default)

When calling the basic methods, the "default" values of each of these will be used. The default values are defined on the CsQuery.Config object (the "default defaults" are shown here -- if you change these on the config object, your new values will be used whenever a default is requested):

    CsQuery.Config.HtmlParsingOptions = HtmlParsingOptions.None;
    CsQuery.Config.DocType = DocType.HTML5;
Note that HtmlParsingOptions is a [Flags] enum. This means you can specify more than one option. So you could, for example, call Create like this:
    var dom = CQ.Create(someHtml,HtmlParsingOptions.Default | HtmlParsingOptions.IgnoreComments);

If you pass a method both Default and some other option(s), it will merge the default values with any additional options you specified. On the other hand, passing options that do not include Default will result in only the options you passed being used.

The other methods remain more or less unchanged. CreateDocument and CreateFragment now simply call Create using the appropriate HtmlParsingOption to define the intended document type.

    public static CQ CreateDocument(...)
    public static CQ CreateFragment(...)
    public static CQ CreateFromFile(...)
    public static CQ CreateFromUrl(...)
    public static CQ CreateFromUrlAsync(...)

The Create method offers a wide range of options for input and parsing. These other methods were created for convenience and before an API to handle input features had been thought out. Though I don't intend to deprecate them right away, I will not likely extend them to support the various options. Anything you can do with these methods can be done about as easily with `Create` and a helper of some kind. For example, if you want to load a DOM from a file using options other than the defaults, you can just pass `File.Open(..)` to the standard `Create` method.

Render Method Options

The Render method signatures look pretty much the same as 1.2.1.. but a lot has changed behind the scenes. The IOutputFormatter interface, which used to be more or less a placeholder, now runs the show. All output is controlled by OutputFormatters implementing this interface. Any Render method which doesn't explicitly identify an OutputFormatter will be using the default formatter provided by the service locator CsQuery.Config.GetOutputFormatter.

    public static Func<IOutputFormatter> GetOutputFormatter {get;set;}
You can replace the default locator with any delegate that returns IOutputFormatter.. Additionally, you can assign a single instance of a class to the CsQuery.Config.OutputFormatter property, which, if set, will supercede use of service locator. When using this method, the object must be thread safe, since new instances will not be created for each use.

There are a number of built-in IOutputFormatter objects accessible through the static OutputFormatters factory:

    OutputFormatters.HtmlEncodingBasic
    OutputFormatters.HtmlEncodingFull
    OutputFormatters.HtmlEncodingMinimum
    OutputFormatters.HtmlEncodingMinimumNbsp
    OutputFormatters.HtmlEncodingNone
    OutputFormatters.PlainText

Each of these except the last returns an OutputFormatter configured with a particular HtmlEncoder. The last strips out HTML and returns just the text contents (to the best of its ability). The factory also has Create methods that let you configure it with specific DomRenderingOptions too. Complete details of these options are in the Render method documentation.

Bug Fixes

  • Issue #51: Fix an issue with compound subselectors whose target included CSS matches above the level of the context.
  • Fix for :empty could return false when non-text or non-element nodes are present

Other New Features

The completely new HTML parser, input and output models aren't enough for you? Well, there are a couple other minor new features.

  • CsQuery should compile under Mono now, after implementing a suggestion to change to `CsQuery.Utility.JsonSerializer.Deserialize` to avoid an unimplemented Mono framework feature.
  • Added a HasAttr method to test for the presence of a named attribute.
  • Add CSS descriptor for Paged Media Module per Pull Request #40 from @kaleb
  • `CQ.DefaultDocType` has been marked as obsolete and will be removed in a future version. Use `Config.DocType` instead
  • `CQ.DefaultDomRenderingOptions` has been marked as obsolete and will be removed in a future version. Use `Config.DomRenderingOptions` instead.

There are other changes in the complete change log, however, many of them are related to the deprecated parser and no longer relevant.

Thanks To The Community

This is a big project, and the new parser is a huge step forward. I think you'll find this release is fast, stable, flexible, and standards-compliant. I owe a debt to a number of people who suffered through the development and beta releases for the last couple months, without their patience and feedback, this would not have been possible. A bug report is a gift! So thanks to all the givers. The following is a list of all the people who've contributed code or bug reports recently. (If I missed anyone, it wasn't intentional!) Thanks - please keep it coming.

Vitallium (code), kaleb (code), petterek, ilushka85, laurentlbm, martincarlsson, allroadcole, Nico1234, Uncleed, Vids, Arithmomaniac, CJCannon, muchio7, SaltyDH


CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as CsQuery. For documentation and more information please see the GitHub repository and posts about CsQuery on this blog.

Thursday, September 13, 2012

Using your favorite Visual Studio 2010 add-ins/extensions in VS2012

I've just about finished my transition from Visual Studio 2010 to Visual Studio 2012. While this has probably been the easiest of any VS update I can remember, it wasn't without a few painful moments. Here's a summary of the annoyances and the solutions I found.

Uppercase Menus

Why, Microsoft, why? I don't want my menus to shout at me. It just looks so... 1992. Luckily, the fix is a piece of cake and requires adding a registry key:

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0\General] "SuppressUppercaseConversion"=dword:00000001

Or just run this to add it automatically: vs_menu_case.reg

Impenetrable color themes

Metro has is moments, but eliminating any visual distinction between windows, boundaries, and areas is not one of them. Neither of the two themes that come with VS2012 were especially workable for me.

Visual Studio 2012 Color Theme Editor to the rescue. The "blue" theme that comes packaged with this painless extension is comfortingly familiar to those used to VS2010's default scheme. Hooray! I can find the edge of a window again.

Ultrafind (and other non-updated VS2010 extensions)

Did you use Ultrafind with VS2010? If no, I feel sorry for you. If yes, you probably miss it now, since it hasn't been updated.

Not content to wait for an update, I threw caution to the wind and figured I'd see what happens if I just shoehorned it into VS2012. What do you know-- it works. Here's how to get your VS2010 extensions running in VS2012. Warning: I know nothing about what, if any, differences there may be in the extension model from VS2010 to VS2012. This works for me. It's absolutely not guaranteed to work for you or for all extensions, but there's not likely much harm you can do.

1. Locate your VS2010 user extensions folder.

Start by opening up C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.pkgdef which shows you the locations from where extensions are loaded. Anything you've installed will likely be in "UserExtensionsFolder":

"UserExtensionsRootFolder" = "$AppDataLocalFolder$\Extensions"

This is probably located here:

C:\Users\{username}\AppData\Local\Microsoft\VisualStudio\10.0\Extensions

2. Copy them.

Within this folder should be a subfolder for each extension you've installed. Copy just the folders related to the extensions you want to migrate from here to the same folder for VS2012 -- the same path, but with "11.0" instead of "10.0". For Ultrafind, it's Logan Mueller [MSFT].

3. Clear cache.

There are two ".cache" files in the Extensions folder. Just delete them. This step might not be needed; I tried this a couple times with and without. If you don't do it, VS seems to get confused about which extensions are enabled. If you do, you may need to re-enable other extensions that are installed.

3. Enable.

You should now just be able to restart VS2012 and see your extension in the extension manager. Cross you fingers and click Enable.


Build Version Increment (and other add-ins)

You can use a similar technique for add-ins. It's even easier. The one I really care about is Build Version Increment, which seems even less likely that Ultrafind to get an update any time soon (since it was barely updated for VS2010!).

1. Find the add-ins folder.

Go to Tools->Options->Add In Security from within VS to find the add-in search path. (I happen to keep mine in dropbox so they stay in sync across several machines). If you've never touched this, your add-ins are probably located in %VSMYDOCUMENTS%\Addins, which is here:

C:\Users\{username}\Documents\Visual Studio 2010\Addins

I have no idea why it's in a completely different place than extensions. Never question Microsoft logic.

2. Copy files

Like before, just copy the files related to the addins you want to migrate to the same folder for VS 2012. It could be just a single file called "something.addin". For BuildVersionIncrement there's also a DLL.

3. Update version.

Edit the "*.addin" file and look for this section:

..
    <HostApplication>
        <Name>Microsoft Visual Studio</Name>
        <Version>10.0</Version>
    </HostApplication>
    ..

Just change that "10.0" to "11.0" and save. That's all. Restart visual studio. If the add-in isn't immediately available, go to Tools->Add In Manager and it should be listed; you can enable it there.

Monday, August 13, 2012

jQuery :text filter selector deprecated in 1.8

... and why it matters

In the list of things changed for jQuery 1.8, you might miss this one, buried deep in the change log:

#9400: Deprecate :text, :radio, :checkbox, etc. selector extensions

Sure enough.. it's got the scarlet letter "Deprecated" tag. What the...? These jQuery pseudo-selectors are probably the first thing I ever learned about using jQuery. This seems to be a... confusing move at best.

Most of these jQuery extension selectors are easily replaced using longform CSS. Indeed, this is the rationale presented with the original request: they're redundant. For example, :checkbox is literally the same as input[type=checkbox]. While I've always like the terseness of the jQuery aliases, I could live without them.

The problem is specifically with :text selector. The CSS version input[type=text] does not work the same as the jQuery :text selector. This is because when there's no type attribute, :text will select it, and the CSS version will not. CSS works only against actual attributes in the markup. This is important with "text" inputs because "text" is the default value. It's perfectly legal, valid, and even encouraged by some (because it's terse), to omit the "type" attribute for the ubiquitous text input. The simplest possible text input is just <input />.

Behold, a textbox, which you will then style with jQuery...

Text:   Check if you love koala bears
or NOT, since you can't select it without :text!!!

Okay, this is not the end of the world. "Deprecated" is a lot different from "removed." jQuery contains features that were deprecated years ago, and it's not especially likely that this is going to be removed any time soon. But most people are uncomfortable writing new code that uses features they know are slated for future removal. So, starting with jQuery 1.8, you need to either choose to always have a "type" attribute, even though it's not required, or use a feature that's been deprecated to select all "text" inputs.

So, this post is mostly an observation. If at some point in the future :text stopped working, you could always use a simple plugin to replace it. No big deal. But it's certainly a curious feature to remove. It's at the core of jQuery's original purpose: making it easy to work with HTML; filling the void left by the DOM and CSS. The :text filter clearly fills such a void; this change undoes something useful.

Wednesday, August 8, 2012

CsQuery 1.2 Released

CsQuery 1.2 has been released. You can get it from NuGet or from the source repository on GitHub.

This release does not add any significant new features, but is tied with the first formal release of the CsQuery.Mvc framework. This framework simplifies integrating CsQuery into an MVC project by allowing you to intercept the HTML output of a view before it's rendered and inspect or alter it using CsQuery. Additionally, it adds an HtmlHelper method for CsQuery so you can create HTML directly in Razor views. It's on nuget as CsQuery.Mvc.

Breaking Change

Though this change is unlikely to affect many people, it is a significant change to the public API for DOM element creation. Any code which creates DOM elements using "new" such as:

    IDomElement obj = new DomElement("div");
will not compile, and should be replaced with:
    IDomElement obj = DomElement.Create("div");

This was necessary to support a derived object model for complex HTML Element implementations to better implement the browser DOM. Previously, any element-type specific functionality was handled conditionally. This was OK when the DOM model was mostly there to support a jQuery port, but as I have worked to create a more accurate representation of the browser DOM itself, it became clear this was not sustainable going forward

In the new model, some DOM element types will be implemented using classes that derive from DomElement. This means that creating a new element must be done from a factory so that element types which have more specific implementations will be instances of their unique derived type.

Any code that used CQ.Create or Document.CreateElement will be unaffected: this will only be a problem if you had been creating concrete DomElemement instances using new.

Bug Fixes

  • Issue #27 - .Value for some HTML tags not implemented

CsQuery.Mvc

As usual I'm behind on documentation, but the usage of CsQuery.Mvc is simple and there's an example MVC3 project in the github repo.

The CsQuery MVC framework lets you directly access the HTML output from an MVC view. It adds a property Doc to the controller and methods Cq_ActionName that run concurrently with action invocations, letting you manipulate the HTML via CsQuery before it's rendered. There's basic documentation in the readme and there's also an example MVC application showing how to use it. You can also take a look at the CsQuery.Mvc.Tests project which is, itself, an MVC application.

Using the CsQuery HTML helper requires adding a reference to CsQuery.Mvc in Views/web.config as usual for any HtmlHelper extension methods:

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Routing" />
        <add namespace="CsQuery.Mvc"/>
      </namespaces>
   </pages>
</system.web.webPages.razor>
Now you can do this in a Razor view:
@Html.HtmlTag("div").AddClass("someclass").Text("some text");
.. or anything at all that you can do with CsQuery normally, and the HTML output of the CQ object will be inserted inline.

CsQuery is a complete CSS selector engine and jQuery port for .NET4 and C#. It's on NuGet as CsQuery. For documentation and more information please see the GitHub repository and posts about CsQuery on this blog.