Skip to content

Commit 3b8c603

Browse files
committed
Merge pull request #1224 from geraldus/g/fix-1213
Handle operators in haskell-ident-pos-at-point too
2 parents e402e77 + 93e0111 commit 3b8c603

File tree

3 files changed

+194
-83
lines changed

3 files changed

+194
-83
lines changed

haskell-mode.el

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ executable found in PATH.")
600600
(goto-char end)))))))
601601

602602
(defun haskell-ident-at-point ()
603-
"Return the identifier under point, or nil if none found.
603+
"Return the identifier near point going backward or nil if none found.
604604
May return a qualified name."
605605
(let ((reg (haskell-ident-pos-at-point)))
606606
(when reg
@@ -618,44 +618,107 @@ May return a qualified name."
618618
(cons start end)))))))
619619

620620
(defun haskell-ident-pos-at-point ()
621-
"Return the span of the identifier under point, or nil if none found.
622-
May return a qualified name."
623-
(save-excursion
624-
;; Skip whitespace if we're on it. That way, if we're at "map ", we'll
625-
;; see the word "map".
626-
(if (and (not (eobp))
627-
(eq ? (char-syntax (char-after))))
628-
(skip-chars-backward " \t"))
629-
630-
(let ((case-fold-search nil))
631-
(cl-multiple-value-bind (start end)
632-
(list
633-
(progn (skip-syntax-backward "w_") (point))
634-
(progn (skip-syntax-forward "w_") (point)))
635-
;; If we're looking at a module ID that qualifies further IDs, add
636-
;; those IDs.
637-
(goto-char start)
638-
(while (and (looking-at "[[:upper:]]") (eq (char-after end) ?.)
639-
;; It's a module ID that qualifies further IDs.
640-
(goto-char (1+ end))
641-
(save-excursion
642-
(when (not (zerop (skip-syntax-forward
643-
(if (looking-at "\\s_") "_" "w'"))))
644-
(setq end (point))))))
645-
;; If we're looking at an ID that's itself qualified by previous
646-
;; module IDs, add those too.
647-
(goto-char start)
648-
(if (eq (char-after) ?.) (forward-char 1)) ;Special case for "."
649-
(while (and (eq (char-before) ?.)
650-
(progn (forward-char -1)
651-
(not (zerop (skip-syntax-backward "w'"))))
652-
(skip-syntax-forward "'")
653-
(looking-at "[[:upper:]]"))
654-
(setq start (point)))
655-
;; This is it.
656-
(unless (= start end)
621+
"Return the span of the identifier near point going backward.
622+
Returns nil if no identifier found or point is inside string or
623+
comment. May return a qualified name."
624+
(when (not (nth 8 (syntax-ppss)))
625+
;; Do not handle comments and strings
626+
(let (start end)
627+
;; Initial point position is non-deterministic, it may occur anywhere
628+
;; inside identifier span, so the approach is:
629+
;; - first try go left and find left boundary
630+
;; - then try go right and find right boundary
631+
;;
632+
;; In both cases assume the longest path, e.g. when going left take into
633+
;; account than point may occur at the end of identifier, when going right
634+
;; take into account that point may occur at the beginning of identifier.
635+
;;
636+
;; We should handle `.` character very careful because it is heavily
637+
;; overloaded. Examples of possible cases:
638+
;; Control.Monad.>>= -- delimiter
639+
;; Control.Monad.when -- delimiter
640+
;; Data.Aeson..: -- delimiter and operator symbol
641+
;; concat.map -- composition function
642+
;; .? -- operator symbol
643+
(save-excursion
644+
;; First, skip whitespace if we're on it, moving point to last
645+
;; identifier char. That way, if we're at "map ", we'll see the word
646+
;; "map".
647+
(when (and (looking-at (rx eol))
648+
(not (bolp)))
649+
(backward-char))
650+
(when (and (not (eobp))
651+
(eq (char-syntax (char-after)) ? ))
652+
(skip-chars-backward " \t")
653+
(backward-char))
654+
;; Now let's try to go left.
655+
(save-excursion
656+
(if (not (haskell-mode--looking-at-varsym))
657+
;; Looking at non-operator char, this is quite simple
658+
(progn
659+
(skip-syntax-backward "w_")
660+
;; Remember position
661+
(setq start (point)))
662+
;; Looking at operator char.
663+
(while (and (not (bobp))
664+
(haskell-mode--looking-at-varsym))
665+
;; skip all operator chars backward
666+
(setq start (point))
667+
(backward-char))
668+
;; Extra check for case when reached beginning of the buffer.
669+
(when (haskell-mode--looking-at-varsym)
670+
(setq start (point))))
671+
;; Slurp qualification part if present. If identifier is qualified in
672+
;; case of non-operator point will stop before `.` dot, but in case of
673+
;; operator it will stand at `.` delimiting dot. So if we're looking
674+
;; at `.` let's step one char forward and try to get qualification
675+
;; part.
676+
(goto-char start)
677+
(when (looking-at-p (rx "."))
678+
(forward-char))
679+
(let ((pos (haskell-mode--skip-qualification-backward)))
680+
(when pos
681+
(setq start pos))))
682+
;; Finally, let's try to go right.
683+
(save-excursion
684+
;; Try to slurp qualification part first.
685+
(skip-syntax-forward "w_")
686+
(setq end (point))
687+
(while (and (looking-at (rx "." upper))
688+
(not (zerop (progn (forward-char)
689+
(skip-syntax-forward "w_")))))
690+
(setq end (point)))
691+
;; If point was at non-operator we already done, otherwise we need an
692+
;; extra check.
693+
(while (haskell-mode--looking-at-varsym)
694+
(forward-char)
695+
(setq end (point))))
696+
(when (not (= start end))
657697
(cons start end))))))
658698

699+
(defun haskell-mode--looking-at-varsym ()
700+
"Return t when point stands at operator symbol."
701+
(when (not (eobp))
702+
(let ((lex (haskell-lexeme-classify-by-first-char (char-after))))
703+
(or (eq lex 'varsym)
704+
(eq lex 'consym)))))
705+
706+
(defun haskell-mode--skip-qualification-backward ()
707+
"Skip qualified part of identifier backward.
708+
Expects point stands *after* delimiting dot.
709+
Returns beginning position of qualified part or nil if no qualified part found."
710+
(when (not (and (bobp)
711+
(looking-at (rx bol))))
712+
(let ((case-fold-search nil)
713+
pos)
714+
(while (and (eq (char-before) ?.)
715+
(progn (backward-char)
716+
(not (zerop (skip-syntax-backward "w'"))))
717+
(skip-syntax-forward "'")
718+
(looking-at "[[:upper:]]"))
719+
(setq pos (point)))
720+
pos)))
721+
659722
(defun haskell-delete-indentation (&optional arg)
660723
"Like `delete-indentation' but ignoring Bird-style \">\"."
661724
(interactive "*P")

tests/haskell-completions-tests.el

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,7 @@ identifiers and module identifiers."
353353
(should (equal expected (haskell-completions-grab-identifier-prefix)))
354354
(should (equal expected (haskell-completions-grab-prefix)))
355355
(insert " (T.pack (\"Hello")
356-
(setq expected
357-
(list 108 113 "Hello" 'haskell-completions-general-prefix))
356+
(setq expected nil)
358357
(should (equal expected (haskell-completions-grab-identifier-prefix)))
359358
(should (equal expected (haskell-completions-grab-prefix)))
360359
(insert " World!\" :: String")
@@ -363,8 +362,7 @@ identifiers and module identifiers."
363362
(should (equal expected (haskell-completions-grab-identifier-prefix)))
364363
(should (equal expected (haskell-completions-grab-prefix)))
365364
(insert " -- Comment")
366-
(setq expected
367-
(list 135 142 "Comment" 'haskell-completions-general-prefix))
365+
(setq expected nil)
368366
(should (equal expected (haskell-completions-grab-identifier-prefix)))
369367
(should (equal expected (haskell-completions-grab-prefix))))
370368
;; test in the middle of line

0 commit comments

Comments
 (0)