46

Currently, the following code:

<!-- language: lang-php -->

    <?php="test"?>

<!-- language: lang-xml -->

    <bug cat="md" name="syntax">

Generates the following markdown:

<?php="test"?>
<bug cat="md" name="syntax">

Remembering the exact syntax to use when I actually have to use this isn't easy though. Is there an elegant way to add a button to the WYSIWYG editor, maybe with a dropdown for language choice?

7
  • 9
    The one that people should use more but don’t is <!-- language: none -->. Without it, random bits of data look really awful. All I can imagine is that folks don’t realize it exists.
    – tchrist
    Feb 15, 2013 at 2:41
  • 2
    Perhaps the default behavior of the {} button could insert <!-- language: none --> which would both solve the "random bits" problem and remind people of the feature. Feb 21, 2013 at 4:39
  • 2
    @tchrist, while I knew about <!-- language: .... -->, I actually had no idea that "none" was a valid option (if I had thought about it, I probably would have guessed it, but it didn't even occur to me). So I think your hypothesis is correct that folks just don't realize it exists
    – Ben Lee
    Feb 21, 2013 at 20:15
  • Just noting I have not +1ed the question or either existing answer because they do not cater for the fact that the current approach of the question's main tag determining the language formatting used works well enough that these hints are only needed occasionally. Of course, when they are need I'd like to easily recall the syntax and available languages too. I've favourited the relevant meta faq/detailed answer that does record these details.
    – Mark Hurd
    May 17, 2013 at 7:41
  • @MarkHurd How would one "cater for this fact"?
    – user200500
    Jun 10, 2013 at 5:00
  • @Asad Both current answers now cater for this, by only displaying when needed.
    – Mark Hurd
    Jun 11, 2013 at 12:16
  • @MarkHurd One case where this isn't true is VB.Net/C# - 99 times out of 100, it doesn't matter if the answer is in either language as it's very simple to convert between them. Because of this, I tend to simply tag .Net - which results in C# highlighting of VB.Net code (works fine until you get to a code comment ' which is interpreted as a string start)
    – Basic
    Oct 18, 2013 at 9:12

2 Answers 2

21
+100

What I think SO needs is:

  • A button next to the a button on the editor toolbar.

Clicking the button would insert:

<!-- language: x -->

The caret would be automatically placed where the x is above, and a JS-driven Intellisense-style dropdown list would populate with some choices, something like this:

a

Typing lang-c would present:

a

My 2¢.

1
  • 2
    Alternatively, a drop down with language selection next to the {} button.
    – Richard
    Feb 22, 2013 at 9:30
9
+75

What it is:

I've worked the kinks out of an implementation of something similar to the suggestion above, which you can test out here (doesn't seem to be working in Chrome, for some reason). It differs in that there is no separate button: once you create a code block, a dropdown appears, giving you the option to add a language hint tag.

The code inside is still pretty horrible, (eg. using jQuery inside code with no jQuery dependency), but it can probably be refactored into something nicer with a little more work.

Screenshots:

enter image description here

enter image description here

How to use:

Adding hints:

  • Select some text and use Ctrl+K or the {} button as normal
  • A dropdown will appear above the newly indented code that you can use to select a language
  • You can either select a language using the dropdown, or click anywhere else to ignore it
  • Note that any existing language hint tags within your selection will be removed. For example, consider the following markdown:

    This is some sample text.
    
    This part is non programming language data.
    

    Select the second line and press Ctrl+K, then select "None" from the dropdown:

    This is some sample text.
    
    <!-- language: lang-none-->
    
        This part is non programming language data.
    

    If you were to now select the entirety of this and press Ctrl+K, then select "Default" in the dropdown, the output would be:

    <!-- language: lang-default-->
    
        This is some sample text.
    
            This part is non programming language data.
    

Removing hints:

  • Select some indented code, including the language hint tag at the beginning
  • Use Ctrl+K or the {} button to dedent code into normal text
  • The language hint is removed

Help please:

If anyone more familiar with the PageDown source can refactor the code, add more features, fix bugs etc., feel free to do so. I've set up a Github repo here.

The only thing I modified was the doCode method of the CommandManager prototype in Markdown.Editor.js:

commandProto.doCode = function (chunk, postProcessing) {

    //Test if there is text preceding the start of the selection on the first selected line
    //or if there is text following the end of the selection on the last selected line
    var hasTextBefore = /\S[ ]*$/.test(chunk.before);
    var hasTextAfter = /^[ ]*\S/.test(chunk.after);

    // Use 'four space' markdown if the selection is on its own
    // line or is multiline.
    if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {

        chunk.before = chunk.before.replace(/[ ]{4}$/,
            function (totalMatch) {
                chunk.selection = totalMatch + chunk.selection;
                return "";
            });

        var nLinesBack = 1;
        var nLinesForward = 1;

        if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
            nLinesBack = 0;
        }
        if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
            nLinesForward = 0;
        }

        chunk.skipLines(nLinesBack, nLinesForward);

        var langPos = chunk.before.length;

        //Retrieve textarea and define dropdown creator
        var txt = this.panels.input;
        function dropdown(){
            var pos,
            langs = {
                "None":"lang-none",
                "Default":"lang-default",
                "Bash":"lang-bsh",
                "C":"lang-c",
                "C++":"lang-cpp",
                "C#":"lang-cs",
                "CSH? I don't know":"lang-csh",
                "CSS":"lang-css",
                "Haskell":"lang-hs",
                "HTML":"lang-html",
                "Java":"lang-java",
                "JavaScript":"lang-js",
                "Lisp":"lang-lisp",
                "Lua":"lang-lua",
                "F#":"lang-ml",
                "Perl":"lang-perl",
                "PHP":"lang-php",
                "Python":"lang-py",
                "Proto":"lang-proto",
                "Ruby":"lang-rb",
                "Scala":"lang-scala",
                "Shell":"lang-sh",
                "SQL":"lang-sql",
                "Visual Basic":"lang-vb",
                "XML":"lang-xml"
            };

            //Use plugin to find coordinates of caret
            $(txt).textareaHelper();
            pos = $(txt).textareaHelper('caretPos');
            $(txt).textareaHelper('destroy');

            //Account for offset of textarea
            pos.top += $(txt).offset().top
            pos.left = $(txt).offset().left
            console.log(pos);

            //Populate dropdown with options
            var langpicker = $('<select>');
            langpicker.append(function(){
                var option = $("<option>")
                            .prop({value:""})
                            .html("Select a language");
                return option;
            })
            $.each(langs, function(i, v){
                var option = $("<option>")
                            .prop({value:"<!-- language: " + v + "-->"})
                            .html(i);
                langpicker.append(option);
            });

            //Position dropdown
            langpicker.appendTo($('body')).css({
                position: 'absolute', 
                left: pos.left + 2, 
                top: pos.top - langpicker.outerHeight(),
                width: "200px"
            })
            .hide()
            .fadeIn()
            .focus()
            .on('change blur', select);

            //Callback function
            function select(e){
                var that = this;
                if(e.type == "change"){
                    $(txt).val(function(i, v){
                        console.log(langPos, v.slice(langPos));
                        return v.slice(0, Math.max(langPos,0)) + $(that).val() + "\n\n" + v.slice(Math.max(langPos,0));
                    });
                }
                $(this).fadeOut(function(){
                    $(this).remove();
                });
            }
        }

        if (!chunk.selection) {
            chunk.startTag = "    ";
            chunk.selection = this.getString("codeexample");
        }
        else {
            //If the block isn't already code, add 4 spaces to the beginning of each line
            if (/^(?![ ]*<\!-- language: lang-[a-z]+-->)[ ]{0,3}\S/m.test(chunk.selection)) {
                if (/\n/.test(chunk.selection))
                    chunk.selection = chunk.selection.replace(/^[ ]{0,3}<\!-- language: lang-[a-z]+-->\n{0,2}/gm, "").replace(/^/gm, "    ");
                else // if it's not multiline, do not select the four added spaces; this is more consistent with the doList behavior
                    chunk.before += "    ";

                txt.setSelectionRange(langPos, langPos);
                setTimeout(dropdown, 0);
            }
            //If the block already is code, remove spaces from the beginning of each line
            else {
                chunk.selection = chunk.selection.replace(/(?:(\n)?[ ]{4}|[ ]{0,3}\t)|(?:^<\!-- language: lang-[a-z]+-->\n*)/g, "$1");
            }
        }

    }
    else {
        // Use backticks (`) to delimit the code block.

        chunk.trimWhitespace();
        chunk.findTags(/`/, /`/);

        if (!chunk.startTag && !chunk.endTag) {
            chunk.startTag = chunk.endTag = "`";
            if (!chunk.selection) {
                chunk.selection = this.getString("codeexample");
            }
        }
        else if (chunk.endTag && !chunk.startTag) {
            chunk.before += chunk.endTag;
            chunk.endTag = "";
        }
        else {
            chunk.startTag = chunk.endTag = "";
        }
    }
};
11
  • IMO, this is better than my answer. Feb 24, 2013 at 2:48
  • @DannyBeckett This one does look fancy, but it comes at the expense of ease of use; the dropdown popping up out of nowhere might be confusing to some people. I think a button in the tools menu is a good idea.
    – user200500
    Feb 24, 2013 at 4:30
  • If each of the languages starts with lang-, then there is no need to define it in each of the languages. You currently have this .prop({value:""}), where you could turn it into .prop({"lang-" + value:""}) May 29, 2013 at 5:23
  • @CodyGuldner Good point. You can edit the answer to improve it.
    – user200500
    May 29, 2013 at 13:58
  • Is there a bug? I have to press that {} button or Ctrl+K 3 times in order to see the drop-down menu.
    – Antony
    Jun 10, 2013 at 4:48
  • @Antony In Firefox?
    – user200500
    Jun 10, 2013 at 4:49
  • @Asad Yes, as you said it doesn't work on Chrome. The same thing happens on Safari.
    – Antony
    Jun 10, 2013 at 4:49
  • @Antony I'll try debugging this now.
    – user200500
    Jun 10, 2013 at 4:51
  • @Antony I'm getting a bunch of Refused to execute script from [script URI] because its MIME type ('text/plain') is not executable. That's what happens when you try to use GitHub as a CDN I guess.
    – user200500
    Jun 10, 2013 at 4:53
  • @Asad I guess there isn't a bug. It's just that I used it wrongly. But maybe you can make it work when someone just start by pressing the {} button without writing any codes.
    – Antony
    Jun 10, 2013 at 4:53
  • @Asad You can use the gh-pages to serve js to avoid this error.
    – Antony
    Jun 10, 2013 at 4:54

Not the answer you're looking for? Browse other questions tagged .