mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
Although unlikely to be valid syntax in most programming languages, such a modeline is valid syntax in Vim, and will trigger any filetype modes.
97 lines
3.2 KiB
Ruby
97 lines
3.2 KiB
Ruby
module Linguist
|
|
module Strategy
|
|
class Modeline
|
|
EMACS_MODELINE = /-\*-\s*(?:(?!mode)[\w-]+\s*:\s*(?:[\w+-]+)\s*;?\s*)*(?:mode\s*:)?\s*([\w+-]+)\s*(?:;\s*(?!mode)[\w-]+\s*:\s*[\w+-]+\s*)*;?\s*-\*-/i
|
|
VIM_MODELINE = /
|
|
|
|
# Start modeline. Could be `vim:`, `vi:` or `ex:`
|
|
(?:
|
|
(?:\s|^)
|
|
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
|
|
: (?=\s* set? \s [^\n:]+ :) |
|
|
|
|
# Otherwise, it isn't valid syntax and should be ignored
|
|
: (?!\s* set? \s)
|
|
)
|
|
|
|
# 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)
|
|
(?:
|
|
\s
|
|
|
|
|
\s* : \s* # 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`
|
|
\s*=
|
|
|
|
# Option's value. Might be blank; `vim: ft= ` says "use no filetype".
|
|
(?:
|
|
[^\\\s] # Beware of escaped characters: titlestring=\ ft=ruby
|
|
| # will be read by Vim as { titlestring: " ft=ruby" }.
|
|
\\.
|
|
)*
|
|
)?
|
|
)*
|
|
|
|
# The actual filetype declaration
|
|
[\s:] (?:filetype|ft|syntax) \s*=
|
|
|
|
# Language's name
|
|
(\w+)
|
|
|
|
# Ensure it's followed by a legal separator
|
|
(?=\s|:|$)
|
|
/xi
|
|
|
|
MODELINES = [EMACS_MODELINE, VIM_MODELINE]
|
|
|
|
# Scope of the search for modelines
|
|
# Number of lines to check at the beginning and at the end of the file
|
|
SEARCH_SCOPE = 5
|
|
|
|
# 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.
|
|
def self.call(blob, _ = nil)
|
|
header = blob.lines.first(SEARCH_SCOPE).join("\n")
|
|
footer = blob.lines.last(SEARCH_SCOPE).join("\n")
|
|
Array(Language.find_by_alias(modeline(header + footer)))
|
|
end
|
|
|
|
# Public: Get the modeline from the first n-lines of the file
|
|
#
|
|
# Returns a String or nil
|
|
def self.modeline(data)
|
|
match = MODELINES.map { |regex| data.match(regex) }.reject(&:nil?).first
|
|
match[1] if match
|
|
end
|
|
end
|
|
end
|
|
end
|