How can I selectively load a language based on file content?


#1

There is a language package I like called “language-TIS-100”. The TIS-100 files all end in “.txt” so they load as “Plain Text”, but they always start with the same two characters: “@0”. I would like to modify the package such that any file with the “.txt” extension and starting with “@0” uses “TIS-100” instead of “Plain Text”.

I asked on IRC for some help, and I learned a little about atom.workspace.observeTextEditors() and atom.grammars.grammarForScopeName(). Here is the code that I have been trying:

'use babel';

export default {
    activate(state) {
        atom.workspace.observeTextEditors(editor => {
            if (editor.getTextInBufferRange([[0, 0], [0, 1]]) == '@0') {
                editor.setGrammar(atom.grammars.grammarForScopeName('source.asm.TIS'))
            }
        });
    }
}

This is in main.js in the root of my dev package, and I have modified the package.json to use main.js as the main – the current package on GitHub has no main.

I don’t really know what I’m doing. Can someone help me out?


#2

Language packages generally aren’t activated on startup. They’re activated when a file that uses that grammar is opened. So putting something like this in a language package’s activate function isn’t going to achieve what you’re looking for.

What you’re really looking for is the firstLineMatch property in the grammar definition. For example, from the language-shellscript package:

This is what activates the language-shellscript package whenever any file is opened that starts with a shebang line like #!/bin/bash.


#3

That is exactly what I was looking for. Thank you so much for your help!


#4

Alas, this doesn’t seem to work. :confounded:

I added the following line to grammars/TIS-100-assembly.cson: 'firstLineMatch': '@0'. I then opened a file named 00150.0.txt with the following contents:

@0
## GOOD SOLUTION
MOV UP, DOWN

@1
MOV RIGHT, DOWN

@2
MOV UP, LEFT

@3
MOV UP, DOWN

@4
MOV UP, DOWN

@5
MOV UP, DOWN

@6
MOV UP, RIGHT

@7
MOV LEFT, DOWN

The mode did not change from Plain Text. Any ideas on why?


#5

Probably either because the CSON isn’t correctly formed and Atom doesn’t know what it’s reading, or you didn’t reload Atom after adding the line.


#6

It’s possible the CSON isn’t correctly formed. I looked online for a validator and didn’t see anything obvious, which is a shame. That being said, it’s a single line which I have quoted above so I don’t think that’s the problem.

I was able to confirm that Atom was reading my package, because I changed the README and the change was visible.


#7

You could still share it just in case that’s the issue.


#8

I have made two changes to this file: removed the fileTypes list because they were wrong, and added the firstLineMatch line.

I can make a gist of the file I posted earlier in the thread if you think that would help.


#9

I was able to fix this by adding a beginning-of-line character to the regular expression. firstLineMatch: '^@0' works for me.

A couple of other things: you have two scopeName attributes. I believe the second one will take priority, but you should just have one. You also don’t need quotes around single-word keys; you certainly can, but I find CSON easier to read with the keys and values being colored differently.


#10

The grammar file came with both scopeName lines, and I wasn’t sure which to delete. I’ll remove the first one.

I changed the regular expression to match yours – 'firstLineMatch': '^@0' – and still got no success.

Here is my input file:

It comes up Plain Text every time, even when I verify that I’m running the correct code by looking at the packages menu, selecting the development package, and then clicking the view code button. :frowning:


#11

You’re right. I screwed up my testing and wasn’t properly clearing the previous grammar, so Atom’s serialization was getting me.

After some more messing around, I’m not sure that there’s a good way to do this since your code files are going to be .txt. Atom greatly prefers to match by file type. So I went back to your original idea and made that work. This is the code I used to test the algorithm. If you plug it directly into your init.coffee, you can see it in action.

# Set grammar to TIS if first line is @0
#atom.commands.add 'atom-text-editor', 'user:set-tis', ->
atom.workspace.observeTextEditors (editor) ->
  editor.onDidSave () ->
    if editor.lineTextForBufferRow(0) == '@0'
      editor.setGrammar(atom.grammars.grammarForScopeName('source.asm.tis'))
      atom.notifications.addSuccess('A TIS file has been identified')
    else
      atom.notifications.addError('No TIS file')

The commented out line of code is an alternative first line that defines a command that you can use to activate it on a keypress instead of via onDidSave. I then went and added another listener to respond to when a file is opened, which is very slightly different:

# Set grammar to TIS if first line is @0
atom.workspace.observeTextEditors (editor) ->
  editor.onDidSave () ->
    if editor.lineTextForBufferRow(0) == '@0'
      editor.setGrammar(atom.grammars.grammarForScopeName('source.asm.tis'))

atom.workspace.onDidOpen (editor) ->
  if editor.item.lineTextForBufferRow(0) == '@0'
    editor.item.setGrammar(atom.grammars.grammarForScopeName('source.asm.tis'))

This code will quietly override the grammar for any file that starts with @0. There’s not even a flicker (though there might be if you opened a very large file).


#12

This works perfectly! Thanks for being patient with me.