Class: Linguist::Strategy::Modeline

Inherits:
Object
  • Object
show all
Defined in:
lib/linguist/strategy/modeline.rb

Constant Summary collapse

EMACS_MODELINE =
/
  -\*-
  (?:
    # Short form: `-*- ruby -*-`
    \s* (?= [^:;\s]+ \s* -\*-)
    |
    # Longer form: `-*- foo:bar; mode: ruby; -*-`
    (?:
      .*?       # Preceding variables: `-*- foo:bar bar:baz;`
      [;\s]     # Which are delimited by spaces or semicolons
      |
      (?<=-\*-) # Not preceded by anything: `-*-mode:ruby-*-`
    )
    mode        # Major mode indicator
    \s*:\s*     # Allow whitespace around colon: `mode : ruby`
  )
  ([^:;\s]+)    # Name of mode

  # Ensure the mode is terminated correctly
  (?=
    # Followed by semicolon or whitespace
    [\s;]
    |
    # Touching the ending sequence: `ruby-*-`
    (?<![-*])   # Don't allow stuff like `ruby--*-` to match; it'll invalidate the mode
    -\*-        # Emacs has no problems reading `ruby --*-`, however.
  )
  .*?           # Anything between a cleanly-terminated mode and the ending -*-
  -\*-
/xi
VIM_MODELINE =
/

  # Start modeline. Could be `vim:`, `vi:` or `ex:`
  (?:
    (?:[ \t]|^)
    vi
    (?:m[<=>]?\d+|m)? # Version-specific modeline
    |
    [\t\x20] # `ex:` requires whitespace, because "ex:" might be short for "example:"
    ex
  )

  # If the option-list begins with `set ` or `se `, it indicates an alternative
  # modeline syntax partly-compatible with older versions of Vi. Here, the colon
  # serves as a terminator for an option sequence, delimited by whitespace.
  (?=
    # So we have to ensure the modeline ends with a colon
    : (?=[ \t]* set? [ \t] [^\n:]+ :) |

    # Otherwise, it isn't valid syntax and should be ignored
    : (?![ \t]* set? [ \t])
  )

  # Possible (unrelated) `option=value` pairs to skip past
  (?:
    # Option separator. Vim uses whitespace or colons to separate options (except if
    # the alternate "vim: set " form is used, where only whitespace is used)
    (?:
      [ \t]
      |
      [ \t]* : [ \t]* # Note that whitespace around colons is accepted too:
    )                 # vim: noai :  ft=ruby:noexpandtab

    # Option's name. All recognised Vim options have an alphanumeric form.
    \w*

    # Possible value. Not every option takes an argument.
    (?:
      # Whitespace between name and value is allowed: `vim: ft   =ruby`
      [ \t]*=

      # Option's value. Might be blank; `vim: ft= ` says "use no filetype".
      (?:
        [^\\[ \t]] # Beware of escaped characters: titlestring=\ ft=ruby
        |          # will be read by Vim as { titlestring: " ft=ruby" }.
        \\.
      )*
    )?
  )*

  # The actual filetype declaration
  [[ \t]:] (?:filetype|ft|syntax) [ \t]*=

  # Language's name
  (\w+)

  # Ensure it's followed by a legal separator
  (?=[ \t]|:|$)
/xi
MODELINES =
[EMACS_MODELINE, VIM_MODELINE]
SEARCH_SCOPE =

Scope of the search for modelines Number of lines to check at the beginning and at the end of the file

5

Class Method Summary collapse

Class Method Details

.call(blob, _ = nil) ⇒ Object

Public: Detects language based on Vim and Emacs modelines

blob - An object that quacks like a blob.

Examples

Modeline.call(FileBlob.new("path/to/file"))

Returns an Array with one Language if the blob has a Vim or Emacs modeline that matches a Language name or alias. Returns an empty array if no match.



111
112
113
114
115
116
117
# File 'lib/linguist/strategy/modeline.rb', line 111

def self.call(blob, _ = nil)
  return [] if blob.symlink?

  header = blob.first_lines(SEARCH_SCOPE).join("\n")
  footer = blob.last_lines(SEARCH_SCOPE).join("\n")
  Array(Language.find_by_alias(modeline(header + footer)))
end

.modeline(data) ⇒ Object

Public: Get the modeline from the first n-lines of the file

Returns a String or nil



122
123
124
125
# File 'lib/linguist/strategy/modeline.rb', line 122

def self.modeline(data)
  match = MODELINES.map { |regex| data.match(regex) }.reject(&:nil?).first
  match[1] if match
end